Dockerizing dwd-api-fastapi
First API: dwd-api-fastapi (repo clone under repos/dwd-api-fastapi/).
Repo summary
| Item | Value |
|---|---|
| Framework | FastAPI |
| ASGI entry | main:app |
| Production server | Gunicorn + uvicorn.workers.UvicornWorker |
| Config | gunicorn.conf.py (already Azure-ready) |
| Python | 3.11 (runtime.txt) |
| Core deps | requirements.txt |
| ML extras (optional) | requirements-ml.txt (tsai, wandb, PyTorch — large) |
| Health probe | GET /health |
| Env template | .env.example |
The repo is already close to container-ready:
startup.shruns gunicorn with the config file.gunicorn.conf.pyreadsPORT(default8080) and sends logs to stdout whenPORTis set — ideal for Docker.- TSAI is optional; without
requirements-ml.txtthe app falls back to scikit-learn (same as Azure production).
Recommended api-name and ports
| Setting | Value |
|---|---|
<api-name> | dwd-api-fastapi |
| Host port | 8001 |
| Container port | 8080 |
| Host bind | 127.0.0.1:8001:8080 |
Required environment variables
From .env.example — mount via /etc/dwd-api-fastapi/dwd-api-fastapi.env:
| Variable | Required | Purpose |
|---|---|---|
DWD_API_BASE_URL | Yes (unless clients always pass base_url) | ASP.NET v2 results API root |
OPENAI_API_KEY | For /agent only | Copilot / MAF agent |
OPENAI_CHAT_MODEL_ID | No | Default gpt-4o-mini |
MCP_SERVER_URL | No | MCP tools for agent |
WANDB_* | No | Forecast experiment tracking |
Image choice: slim vs ml
| Variant | Install | Size | Forecast engine | Use when |
|---|---|---|---|---|
| slim | requirements.txt only | ~500 MB order of magnitude | scikit-learn fallback | Production default; matches Azure |
| ml | requirements.txt + requirements-ml.txt | Several GB (PyTorch) | tsai | TSAI required in prod |
Recommendation: Start with slim. Add an ml build target later if forecasts must use tsai on this server.
Step 1 — Add files to the git repo
Per-service build files are in the submodule repo root:
Dockerfile.dockerignore
Stack orchestration (Compose, cross-service env, Nginx) lives in hydrotrek-dwd-suite:
Dockerfile templates: templates/docker/dwd-api-fastapi/ (Dockerfile, .dockerignore).
Local build verified: docker build --target slim -t dwd-api-fastapi:slim . — /health returns 200 on 127.0.0.1:8001.
Step 2 — Build and test locally (Windows)
Single service (image only):
cd "c:\jacob\admin\GQC Computers\MSI Laptop 3\repos\dwd-api-fastapi"
docker build --target slim -t dwd-api-fastapi:slim .
Full stack (FastAPI + MCP + Copilot on dwd-stack):
cd "c:\jacob\admin\GQC Computers\MSI Laptop 3\repos\hydrotrek-dwd-suite"
git submodule update --init dwd-api-fastapi dwd-mcp-fastmcp dwd-copilot-server
cd docker
# Copy env/*.env.example → env/*.env and edit
docker compose up -d --build
Verify:
curl http://127.0.0.1:8001/health
curl http://127.0.0.1:8001/docs
Step 3 — Interim native deploy (optional)
If the server needs the API before Docker is installed, follow 02-adding-a-new-api.md with:
<api-name>=dwd-api-fastapi<module>:<app>=main:app- Use gunicorn in the systemd unit (matches existing
dwd-api.servicein the repo)
ExecStart=/opt/venvs/dwd-api-fastapi/bin/gunicorn --config gunicorn.conf.py main:app
Set PORT=8001 and bind in gunicorn — or adjust gunicorn.conf.py bind for native 127.0.0.1:8001. The repo defaults to 0.0.0.0:8080; on the host use:
PORT=8001 GUNICORN_WORKERS=2
and ensure Nginx points at 127.0.0.1:8001.
Native and Docker can coexist during migration; only one should bind port 8001.
Step 4 — Docker on the Ubuntu server
When Docker is installed:
# One-time
sudo apt install docker.io docker-compose-v2
# Secrets (same as native path)
sudo mkdir -p /etc/dwd-api-fastapi
sudo cp dwd-api-fastapi.env /etc/dwd-api-fastapi/dwd-api-fastapi.env
sudo chmod 640 /etc/dwd-api-fastapi/dwd-api-fastapi.env
sudo chown root:apisvc /etc/dwd-api-fastapi/dwd-api-fastapi.env
# Compose file for production (stack — all three APIs)
sudo mkdir -p /etc/dwd-stack
sudo chown root:apisvc /etc/dwd-stack
sudo chmod 750 /etc/dwd-stack
sudo cp /opt/apis/hydrotrek-dwd-suite/docker/docker-compose.prod.yml /etc/dwd-stack/docker-compose.yml
# Or FastAPI-only (legacy): copy from hydrotrek-dwd-suite/docker and trim to one service
# systemd (stack example)
# ExecStart=/usr/bin/docker compose -f /etc/dwd-stack/docker-compose.yml up -d --remove-orphans
Production compose pulls a tagged image (when CI exists) or builds from /opt/apis/dwd-api-fastapi initially.
Step 5 — Nginx
Unchanged from 04-nginx-reverse-proxy.md. Upstream remains:
server 127.0.0.1:8001;
Repo hygiene before first image build
The local clone may contain wandb/ run artifacts. They are gitignored but would bloat the build context. The template .dockerignore excludes them. Remove from disk if present:
Remove-Item -Recurse -Force wandb -ErrorAction SilentlyContinue
Differences from existing dwd-api.service in repo
The repo’s bundled dwd-api.service targets /opt/dwd-api-fastapi with user www-data. Our server standard uses:
| Repo file | Server standard |
|---|---|
/opt/dwd-api-fastapi | /opt/apis/dwd-api-fastapi (native) or image-only (Docker) |
www-data | apisvc (native) or Docker (container user) |
File logs under /var/log/gunicorn | stdout in Docker; journald on host |
Do not copy the repo’s dwd-api.service verbatim — use templates/api.service.template or templates/docker/api.service.docker.template.
Next actions
- Review template
Dockerfileand adjust worker counts for laptop hardware. - Commit Docker files to the dwd-api-fastapi git repository.
- Test slim image locally with a real
DWD_API_BASE_URL. - Decide native-first vs wait for Docker on server.
- When ready, we can add a GitHub Actions workflow to build and push to GHCR/ACR.