Deploying FastAPI on Ubuntu with systemd, Nginx, and Docker
This is a hardened deploy path for a modern FastAPI service: one Docker image, Gunicorn/Uvicorn workers, Nginx TLS termination, and systemd supervision with blue-green rollouts and instant rollback.
1) Project Layout
.
app/
├── main.py
├── requirements.txt
├── gunicorn_conf.py
└── Dockerfile
2) App Entry
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"ok": True}
3) Gunicorn/Uvicorn Config
# gunicorn_conf.py
bind = "0.0.0.0:8000"
workers = 2
worker_class = "uvicorn.workers.UvicornWorker"
accesslog = "-"
errorlog = "-"
timeout = 30
graceful_timeout = 30
4) Dockerfile (multi-stage)
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS base
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM base AS runtime
COPY . .
EXPOSE 8000
CMD ["gunicorn", "-c", "gunicorn_conf.py", "main:app"]
5) Build & Run
docker build -t redesign/fastapi:1.0.0 .
docker run -d --name fastapi -p 8000:8000 redesign/fastapi:1.0.0
6) Nginx Reverse Proxy (+ TLS)
# /etc/nginx/sites-available/fastapi.conf
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 65;
}
}
Enable site, then add TLS via certbot --nginx -d api.example.com.
7) systemd Service (supervision & restart)
# /etc/systemd/system/fastapi.service
[Unit]
Description=FastAPI container
After=docker.service
Requires=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker run --rm --name fastapi -p 8000:8000 \
--env-file /etc/fastapi.env redesign/fastapi:1.0.0
ExecStop=/usr/bin/docker stop fastapi
[Install]
WantedBy=multi-user.target
Then sudo systemctl enable --now fastapi.
8) Blue-Green Deploys
docker pull redesign/fastapi:1.1.0
docker run -d --name fastapi-blue -p 8001:8000 --env-file /etc/fastapi.env redesign/fastapi:1.1.0
# Switch Nginx upstream to 127.0.0.1:8001, reload Nginx, drain old container, stop it.
9) Health Checks & Logs
- Expose
/healthand/metrics(Prometheus) for probes. - Log to stdout; let the platform (journald/Loki) handle storage and rotation.
- Set
readinessdelays to avoid cold-start spikes post-deploy.
10) Secrets & Config
- Use
--env-fileor Docker secrets; never bake secrets into images. - Prefer short-lived tokens; rotate via systemd reload and rolling restart.
11) Rollback
# Switch Nginx back to the previous upstream (green),
# stop the faulty version, and pin the tag.
docker stop fastapi-blue
docker tag redesign/fastapi:1.0.0 redesign/fastapi:stable
“Ship thin. Observe deeply. Roll forward with grace.” — redesign.ir
Tip: Keep
stable and next tags for human-friendly rollouts; protect latest from accidental use in production.Warning: Don’t bind Docker to 0.0.0.0 on public hosts without Nginx/TLS in front—exposes your app to the internet unfiltered.
Comments
Join the discussion. We keep comments private to your device until moderation tooling ships.