From 35f0fb7c0b57221889c8093affd8b7e8497798dc Mon Sep 17 00:00:00 2001 From: tmwgsicp <2589462900@qq.com> Date: Tue, 24 Mar 2026 02:21:14 +0800 Subject: [PATCH] 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 --- .dockerignore | 57 +++++++++++++++++++++++++ .github/workflows/docker-publish.yml | 64 ++++++++++++++++++++++++++++ Dockerfile | 61 ++++++++++++++++++++++++++ docker-compose.yml | 31 ++++++++++++++ env.example | 3 ++ utils/rss_store.py | 5 ++- 6 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-publish.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a54682e --- /dev/null +++ b/.dockerignore @@ -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 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..5ecf057 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..589d61a --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..32f8e55 --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/env.example b/env.example index fe1c1ef..245751f 100644 --- a/env.example +++ b/env.example @@ -42,3 +42,6 @@ SITE_URL=http://localhost:5000 PORT=5000 HOST=0.0.0.0 DEBUG=false + +# 数据库路径(默认 ./data/rss.db,Docker 环境一般无需修改) +# RSS_DB_PATH=/app/data/rss.db diff --git a/utils/rss_store.py b/utils/rss_store.py index e8f27c6..fabad38 100644 --- a/utils/rss_store.py +++ b/utils/rss_store.py @@ -12,12 +12,15 @@ RSS 数据存储 — SQLite import sqlite3 import time import logging +import os from pathlib import Path from typing import List, Dict, Optional 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: