Last Updated: 2026-02-25
Issue: #69 OPS-07 containerized deployment baseline
This runbook defines the minimal production-oriented container baseline for Taskdeck:
- backend API image
- frontend static image
- reverse proxy entrypoint with compression and proxy security headers
Hardening verification follow-through:
#142OPS-16 deployment/container hardening verification matrix (docs/ops/DEPLOYMENT_HARDENING_MATRIX.md)
Terraform follow-through:
#102OPS-10 Terraform/IaC baseline (docs/ops/DEPLOYMENT_TERRAFORM_BASELINE.md)
Staged deployment workflow:
#101OPS-09 staged deployment with blue/green and canary (docs/ops/DEPLOYMENT_WORKFLOW.md)- Release verification checklist:
docs/ops/RELEASE_CHECKLIST.md - ADR:
ADR-0028
deploy/docker/backend.Dockerfiledeploy/docker/frontend.Dockerfiledeploy/docker-compose.ymldeploy/nginx/reverse-proxy.confdeploy/nginx/frontend.confdeploy/.env.example
When Terraform is used, these compose assets remain the workload layer. The Terraform baseline provisions the host/network/storage shell around them; it does not replace the compose deployment model.
Secret handling for all environments follows docs/security/SECRETS_MANAGEMENT_BASELINE.md.
For compose deployments, TASKDECK_JWT_SECRET in deploy/.env is required and enforced at render time.
- Docker Engine with
docker composesupport - Repository checked out at target commit
docker build -f deploy/docker/backend.Dockerfile -t taskdeck-api:local .
docker build --build-arg VITE_API_BASE_URL=/api -f deploy/docker/frontend.Dockerfile -t taskdeck-web:local .- Copy env defaults and replace secrets:
cp deploy/.env.example deploy/.env- Start stack:
docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile baseline up -d --build- Validate:
- App entrypoint:
http://localhost:8080 - API via proxy:
http://localhost:8080/api - Optional (only when backend runs with
ASPNETCORE_ENVIRONMENT=Development):http://localhost:8080/swagger
- Stop stack:
docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile baseline downPowerShell helpers from repo root:
powershell -File ./scripts/deploy/Upgrade-DockerDesktop.ps1
powershell -File ./scripts/deploy/Check-ContainerHost.ps1
powershell -File ./scripts/deploy/Build-TaskdeckImages.ps1
powershell -File ./scripts/deploy/Start-TaskdeckStack.ps1 -Build
powershell -File ./scripts/deploy/Smoke-TestTaskdeckStack.ps1
powershell -File ./scripts/deploy/Stop-TaskdeckStack.ps1Start-TaskdeckStack.ps1 now waits for proxy readiness (/health/ready) by default before returning.
Use -SkipReadyWait only when you intentionally want fire-and-forget startup behavior.
Run the full hardening matrix automation from repo root:
powershell -File ./scripts/deploy/Verify-TaskdeckDeploymentHardening.ps1 -Port 8080This command verifies:
- required secret enforcement for compose rendering
- reverse-proxy security header posture
- unauthorized endpoint behavior through the proxy (
401) - startup/restart/shutdown reliability for the baseline stack
Matrix details and pass/fail criteria:
docs/ops/DEPLOYMENT_HARDENING_MATRIX.md
- Pin environment values in
deploy/.env(especiallyTASKDECK_JWT_SECRET). - Build both images from repo root using the exact commit SHA.
- Export portable artifacts:
docker save taskdeck-api:local | gzip -1 > taskdeck-api.tar.gz
docker save taskdeck-web:local | gzip -1 > taskdeck-web.tar.gz
sha256sum taskdeck-api.tar.gz taskdeck-web.tar.gz > taskdeck-images.sha256- Load on staging host:
docker load -i taskdeck-api.tar.gz
docker load -i taskdeck-web.tar.gz
docker compose -f deploy/docker-compose.yml --env-file deploy/.env --profile baseline up -dPowerShell export helper:
powershell -File ./scripts/deploy/Export-TaskdeckImages.ps1This writes uncompressed exports:
artifacts/container-images/taskdeck-api.tarartifacts/container-images/taskdeck-web.tarartifacts/container-images/SHA256SUMS.txt
deploy/nginx/reverse-proxy.conf applies:
- routing:
/-> frontend container/api/*-> backend container/hubs/*-> backend container (WebSocket upgrade enabled)
- forwarded headers:
X-Forwarded-ForX-Forwarded-ProtoX-Forwarded-Host
- response headers:
X-Content-Type-Options: nosniffX-Frame-Options: SAMEORIGINReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: geolocation=(), microphone=(), camera=()Content-Security-Policy: default-src 'self'; ...
- gzip compression for common text/json/js/css/svg payloads
- This baseline terminates HTTP at the containerized Nginx proxy.
- For staging/production TLS, terminate HTTPS at an edge proxy/load balancer and forward plain HTTP only on private network links to this stack.
- Backend forwarded-header handling is enabled in compose via:
ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
- Do not expose the backend container directly to the public internet while trusting forwarded headers.
Health endpoint routing:
/health/readyis routed through reverse proxy to backend for readiness probes.
- SQLite data persists in compose volume
taskdeck-dbmounted at/app/data. - Connection string override in compose:
ConnectionStrings__DefaultConnection=Data Source=/app/data/taskdeck.db
- Local development and compose runs only require Docker Desktop running.
- Registry pulls/pushes in terminal should use
docker loginfor each registry as needed. - CI should use scoped registry tokens/secrets (never personal passwords in scripts or committed files).
CI workflow .github/workflows/ci-required.yml includes container-images job that:
- validates compose rendering
- builds backend and frontend images from repo root
- exports compressed image artifacts and checksums