Deploying to a server
Production runs from Docker images that CI builds and pushes to GitHub Container Registry (GHCR) after every green main build. The server only needs Docker and three files: docker-compose.prod.yml, the prod.sh script and an .env.server with your settings. prod.sh up runs Alembic migrations first and starts the services only if they succeed.
What runs in production?
Section titled “What runs in production?”docker-compose.prod.yml starts the API, a Celery worker, a separate Celery beat (scheduler) and the web container (nginx serving the built frontend and proxying the API). The database and Redis are yours — point .env.server at them.
How do I install it on a new server?
Section titled “How do I install it on a new server?”No repository clone is needed:
mkdir -p /srv/fastapi-react-admin && cd $_base=https://raw.githubusercontent.com/yilecoding/fastapi-react-admin/maincurl -fsSLO $base/docker-compose.prod.ymlcurl -fsSL $base/deploy/prod.sh -o prod.shcurl -fsSL $base/apps/api/deploy/backend/docker-compose/.env.server.example -o .env.server
# Fill in every CHANGE_ME, then hand the file to the container user (uid 1000)vi .env.serverchown 1000:1000 .env.server && chmod 600 .env.server
# Machine-specific values go here; the compose file stays byte-identical to the repocat > .env <<'EOF'FBA_ENV_FILE=./.env.serverFBA_WEB_PORT=8080EOF
bash prod.sh pull && bash prod.sh upprod.sh refuses to start if the config file is missing, readable by group or others, or still contains CHANGE_ME. TLS is left to whatever sits in front (a load balancer, Caddy, an nginx reverse proxy): the stack exposes one plain HTTP port.
Back up the database before the first deployment against an existing one.
Checklist before going live
Section titled “Checklist before going live”- Every
CHANGE_MEin.env.serveris replaced. WithENVIRONMENT='prod'the backend checks length and randomness and refuses to start on weak values -
TOKEN_SECRET_KEYis freshly generated (python -c "import secrets;print(secrets.token_urlsafe(32))"), not copied from another environment -
CORS_ALLOWED_ORIGINSis your real address; behind nginx or a load balancer,TRUSTED_PROXIESis set — otherwise rate limits and IP allow-lists see the proxy’s address - If you use the AI assistant, object storage, group bots or the open platform, their encryption keys are set (
AI_ENCRYPT_SECRET_KEY,STORAGE_ENCRYPT_SECRET_KEY,NOTIFICATION_ENCRYPT_SECRET_KEY,OPEN_SECRET_KEY) — without them saving those settings fails instead of storing plaintext -
NOTIFICATION_WEB_BASE_URLis your real address, or links in emails and group messages point atlocalhost:8888 - The admin password is the strong one set during initialization, not the demo’s
123456 - The database is backed up before the first deployment against it, and scheduled backups exist
- TLS is provided by the reverse proxy or load balancer, and HTTP redirects to HTTPS
- External monitoring probes
/api/health/status, not/healthz - You noted the current image tag (
sha-<commit>) so you know what to roll back to
How do I update or roll back?
Section titled “How do I update or roll back?”bash prod.sh status # which commit is running, and how far behind main it isbash prod.sh pullbash prod.sh upPushing new images does not update servers automatically — deploying is a deliberate step. Every image is also tagged sha-<short SHA> and sha-<full SHA>, so rolling back is a pull of an older tag, without waiting for a build:
TAG=sha-abc1234 bash prod.sh pullTAG=sha-abc1234 bash prod.sh upWhat should external monitoring check?
Section titled “What should external monitoring check?”One URL: https://<your-domain>/api/health/status. It reports the API, database, Redis, scheduler (beat) and worker, and returns 200 only when all of them are healthy, 503 otherwise.
{"status":"ok","services":{"api":{"status":"ok"},"database":{"status":"ok"},"redis":{"status":"ok"},"scheduler":{"status":"ok","last_seen_seconds":12},"worker":{"status":"ok","last_seen_seconds":31}},"heartbeat_max_age_seconds":180}Can I build the images myself?
Section titled “Can I build the images myself?”Yes, when the server can’t reach GHCR — it needs the full repository on that machine:
pnpm deploy:prod:buildpnpm deploy:prodUse either pull or a local build, not both: otherwise which image up runs depends on which command last touched the local image cache.
Further reading
Section titled “Further reading”deploy/README.md— every command (in Chinese).deploy/AGENTS.md— the pitfalls behind these rules, and why automatic deployment was left out on purpose.- Desktop and mobile clients — installers are built by a separate release workflow.