# Portainer / Swarm deployment `web`, PostgreSQL, `ingestion-worker`, and `ingestion-admin` are one stack. Do not deploy ingestion as a separate endpoint: the approved catalog is exchanged through the `catalog_exports` volume mounted read-only by web and read-write by the worker. In Portainer, create a Swarm stack from `deploy/swarm/docker-stack.yml`. Configure these values as stack environment variables: - `WEB_IMAGE` - digest-pinned web image. - `INGESTION_IMAGE` - digest-pinned ingestion image. - `POSTGRES_PASSWORD` - database secret. - `INGESTION_ADMIN_PASSWORD` - Basic Auth password for `/admin`. - `PUBLIC_HOST=videoreg.ru` and `TRAEFIK_NETWORK`. - Optional source/crawl settings: `MVIDEO_DASHCAM_CATEGORY_URL`, daily budgets, and delay bounds. Keep every service on the same manager/placement constraint. The database, snapshots, read model, and shared catalog volume are local Docker volumes and must be backed up from that node. Gitea Actions uses `.gitea/workflows/master.yml`: it tests the repository, builds both images, pushes immutable digests, and deploys this one stack. The workflow connects to the Swarm manager, pulls those digests, and runs `docker stack deploy --with-registry-auth`; Portainer then shows and manages the same updated stack. Portainer does not need registry credentials or a separate webhook-based image build. Verification after deploy: ```bash curl -fsS https://videoreg.ru/health/ready curl -u "$INGESTION_ADMIN_USERNAME:$INGESTION_ADMIN_PASSWORD" \ https://videoreg.ru/admin/api/queue ```