Production deployment
One compose.yml builds every service from the repo root:
backend/— Django API on gunicorn:5000, static via WhiteNoisefrontend/— Vite SPA, built to static files at image build timeweb— the only published container: nginx serving the SPA and reverse proxying the API. No node or bun at runtime.
host.com/ -> nginx (SPA, history fallback to index.html)
host.com/api/ -> backend (Django REST API)
host.com/admin/ -> backend (Django admin)
host.com/static/ -> backend (WhiteNoise: admin/DRF assets)
host.com/media/ -> nginx (shared `media` volume)
Because nginx fronts both, the browser sees one origin and there is no CORS in production.
Files
| File | Purpose |
|---|---|
compose.yml |
database + backend + worker + web |
api.Dockerfile |
Django image (collectstatic at build) |
web.Dockerfile |
node builds frontend/dist, nginx serves it |
nginx.conf |
SPA + API/admin/static/media routing, asset cache headers |
entrypoint.sh |
backend: wait for postgres, migrate, then gunicorn |
Configure
Fill backend/.env (copy from backend/.env.example). For the compose network:
DJANGO_DEBUG=False
SECRET_KEY=<generate one>
POSTGRES_HOST=database
POSTGRES_PORT=5432
POSTGRES_DATABASE=sarlink
POSTGRES_USER=sarlink
POSTGRES_PASSWORD=<strong>
ALLOWED_HOSTS=localhost,127.0.0.1,backend,portal.sarlink.net
CSRF_TRUSTED_ORIGINS=https://portal.sarlink.net
FRONTEND_URL=https://portal.sarlink.net
SMS_API_URL=...
SMS_API_KEY=...
The POSTGRES_* values also feed the database service through compose
defaults, so keep them in sync or export them before up.
The frontend needs no runtime configuration: it calls a relative /api/...
which nginx routes to the backend.
Build & run
docker compose -f .build/prod/compose.yml up -d --build
docker compose -f .build/prod/compose.yml exec backend python manage.py createsuperuser
The site is on http://localhost:8080; remap the web port behind your TLS
terminator. Migrations run on backend startup.
Running from published images
The build pushes git.shihaam.dev/sarlink/sarlinkportal/{backend,web}. To deploy
without building, replace each service's build: block with its image: and
keep database, the volumes, env_file and the web port mapping.