feat: add Docker support with multi-arch build and CI/CD

- Add Dockerfile with multi-stage build for smaller image size
- Add docker-compose.yml for one-command deployment
- Add .dockerignore for optimized build context
- Add GitHub Actions workflow for automated Docker Hub publishing (amd64/arm64)
- Make RSS_DB_PATH configurable via environment variable
- Update env.example with database path option

Fixes #2

Made-with: Cursor
This commit is contained in:
tmwgsicp 2026-03-24 02:21:14 +08:00
parent 94a0b78ca8
commit 35f0fb7c0b
6 changed files with 220 additions and 1 deletions

57
.dockerignore Normal file
View File

@ -0,0 +1,57 @@
# Git
.git/
.gitignore
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
dist/
build/
.eggs/
# Virtual environments
venv/
env/
.venv/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Data (mounted as volume)
data/
*.db
# Logs
*.log
# Cursor/Claude
.cursor/
.claude/
# SaaS version (not for open-source image)
saas/
# Testing
.pytest_cache/
.coverage
htmlcov/
# Docs (not needed in image)
docs/
*.md
!requirements.txt
# Temp
*.tmp
*.bak

64
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,64 @@
# Build and publish Docker image to Docker Hub
# Triggered on version tags (v*) or manual dispatch
name: Docker Publish
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Image tag (e.g., latest, v1.0.0)'
required: true
default: 'latest'
env:
REGISTRY: docker.io
IMAGE_NAME: tmwgsicp/wechat-download-api
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

61
Dockerfile Normal file
View File

@ -0,0 +1,61 @@
# WeChat Download API - Docker Image
# Multi-stage build for smaller image size
FROM python:3.11-slim AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libffi-dev \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip wheel --no-cache-dir --wheel-dir=/app/wheels -r requirements.txt
FROM python:3.11-slim
LABEL maintainer="tmwgsicp"
LABEL description="WeChat Official Account Article Download API with RSS Support"
LABEL version="1.0.0"
WORKDIR /app
# Install runtime dependencies (curl for healthcheck)
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/* \
&& useradd -m -u 1000 appuser
# Copy wheels from builder and install
COPY --from=builder /app/wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
# Copy application code
COPY . .
# Create data directory for SQLite and set permissions
RUN mkdir -p /app/data && chown -R appuser:appuser /app
# Switch to non-root user
USER appuser
# Environment variables with sensible defaults
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
HOST=0.0.0.0 \
PORT=5000 \
DEBUG=false
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD curl -sf http://localhost:5000/api/health || exit 1
# Run with uvicorn
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "5000"]

31
docker-compose.yml Normal file
View File

@ -0,0 +1,31 @@
# WeChat Download API - Docker Compose
# Usage: docker-compose up -d
#
# First time setup:
# 1. Copy env.example to .env: cp env.example .env
# 2. Edit .env and set SITE_URL to your actual URL
# 3. Run: docker-compose up -d
# 4. Visit http://localhost:5000/login.html to scan QR code
services:
wechat-api:
build: .
# Or use pre-built image (after published):
# image: tmwgsicp/wechat-download-api:latest
container_name: wechat-download-api
restart: unless-stopped
ports:
- "5000:5000"
volumes:
# Persist SQLite database
- ./data:/app/data
# Config file (writable - login saves credentials here)
- ./.env:/app/.env
environment:
- TZ=Asia/Shanghai
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:5000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s

View File

@ -42,3 +42,6 @@ SITE_URL=http://localhost:5000
PORT=5000 PORT=5000
HOST=0.0.0.0 HOST=0.0.0.0
DEBUG=false DEBUG=false
# 数据库路径(默认 ./data/rss.dbDocker 环境一般无需修改)
# RSS_DB_PATH=/app/data/rss.db

View File

@ -12,12 +12,15 @@ RSS 数据存储 — SQLite
import sqlite3 import sqlite3
import time import time
import logging import logging
import os
from pathlib import Path from pathlib import Path
from typing import List, Dict, Optional from typing import List, Dict, Optional
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
DB_PATH = Path(__file__).parent.parent / "data" / "rss.db" # Database path: configurable via env var, defaults to ./data/rss.db
_default_db = Path(__file__).parent.parent / "data" / "rss.db"
DB_PATH = Path(os.getenv("RSS_DB_PATH", str(_default_db)))
def _get_conn() -> sqlite3.Connection: def _get_conn() -> sqlite3.Connection: