# Portainer deployment DTF follows the same operating model as Graphs and ComporHUB: Gitea builds prebuilt images, pushes them to the Gitea registry, and calls one Portainer webhook. Portainer owns and redeploys one Docker Swarm stack named `dtf-cloud`. The production stack is `deploy/stack.yaml`. It contains Site, Kanban, API, worker, PostgreSQL, ClamAV, and a one-time database initializer. Production uses Cloudflare R2, so MinIO is not part of this stack. ## 1. Gitea configuration The single workflow is `.gitea/workflows/deploy.yml`. Pull requests run static validation. A push to `main` runs the full isolated test suite, builds and scans the production images, publishes both `latest` and the full commit SHA, then calls Portainer. Repository variables: - `REGISTRY_HOST` — normally `gitea.blyzer.com.br`. - `REGISTRY_OWNER` — normally `blyzer`. - `PYTHON_BASE_IMAGE`, `NGINX_BASE_IMAGE`, `TRIVY_IMAGE` — approved immutable `@sha256:` image references. Repository secrets: - `REGISTRY_USERNAME` and `REGISTRY_TOKEN` — package write credentials. - `PORTAINER_WEBHOOK` — webhook generated by the `dtf-cloud` Portainer stack. The webhook is called only after tests and HIGH/CRITICAL secret, misconfiguration, and image gates pass. The current local-only application fails the source preflight intentionally, so it cannot publish yet. ## 2. One-time Portainer resources Use a Docker Swarm environment. Create a dedicated production PostgreSQL volume and set its name as `POSTGRES_VOLUME`. Label its Swarm node `dtf_database=true`. Do not reuse the localhost Compose volume. Create each application credential under **Secrets**. Use versioned names such as `dtf_prod_database_url_v1`, then place only those names in the corresponding `*_SECRET` stack variables. The stack expects distinct secrets for: - runtime and administrator database URLs; - database administrator and application-role passwords; - R2 access key and secret key; - operator password; - Mercado Pago token and webhook secret; - Tiny/Olist token; - WhatsApp token. Credential values must never be entered into Git, `portainer.env`, or Gitea workflow variables. ## 3. Create the stack once In Portainer select **Stacks → Add stack → Git repository**: - Name: `dtf-cloud` - Repository: this Gitea repository - Reference: `main` - Compose path: `deploy/stack.yaml` - Registry: the private `gitea.blyzer.com.br` registry - Re-pull image: enabled - Automatic update: webhook Load a completed copy of `deploy/portainer.env.example` as the stack's non-secret environment variables. Do not load the example unchanged: `TBD` and zero digests are deliberate blockers. Keep `IMAGE_TAG=latest` for normal webhook deployments. Before entering those values in Portainer, validate the completed ignored file: ```bash set -a . deploy/portainer.env set +a python3 deploy/production_preflight.py ``` The public reverse proxy should send the Site hostname to `SITE_PORT` and the Kanban hostname to `KANBAN_PORT`. `/api/` and `/portal.html` are served through the Site gateway. Preserve the original Host header. Do not expose PostgreSQL, API, worker, or ClamAV. Restrict the two published web ports at the host firewall so only the reverse proxy can use them, and terminate HTTPS at the proxy. The `db-init` service completing and stopping is expected. The other six services must be healthy. A failed `db-init` task or an unhealthy service blocks acceptance. ## 4. Normal deployment Push to `main`. Gitea validates, tests, scans, publishes these images, and calls the webhook: ```text gitea.blyzer.com.br/blyzer/dtf-api:latest gitea.blyzer.com.br/blyzer/dtf-api: gitea.blyzer.com.br/blyzer/dtf-web:latest gitea.blyzer.com.br/blyzer/dtf-web: ``` In Portainer, verify the new image digests, service health, `db-init` result, and the public `/health` endpoints. Back up PostgreSQL and R2 before changes that affect stored data or retention. ## 5. Rollback In the Portainer stack variables, change `IMAGE_TAG` from `latest` to the full SHA of the last known-good Gitea commit and redeploy. This rolls back Site, API, Kanban, and worker code; it does not undo database migrations or restore data. After recovery and a corrected release, return `IMAGE_TAG` to `latest`. ## Current blocker This is the final deployment shape, but it is not authorized for production today. Production adapters, Docker-secret file loading, provider sandbox tests, current clean base images, approved inputs, and a production restore rehearsal are still required. Do not bypass `deploy/production_preflight.py` or the Gitea scan gates to make a deployment run.