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
HOST=0.0.0.0
DEBUG=false
# 数据库路径(默认 ./data/rss.dbDocker 环境一般无需修改)
# RSS_DB_PATH=/app/data/rss.db

View File

@ -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: