# Parallel deployment ## Environments The Gitea workflow deploys two permanent, isolated environments on the target server. The old HTTPS installation is outside this workflow and remains unchanged until a separately approved cutover. | Branch | Compose project | VPN address | Secret | |---|---|---|---| | `main` | `assessor_prod` | `http://10.0.0.154:4173` | `PROD_ENV_FILE` | | `experimental` | `assessor_experimental` | `http://10.0.0.154:4174` | `STAGING_ENV_FILE` | `assessor_prod` already exists on the server. Keep its current `PROD_ENV_FILE` database name, user and password unchanged so the existing PostgreSQL volume remains accessible. Do not rotate only the secret; password rotation requires a coordinated PostgreSQL role change. `STAGING_ENV_FILE` contains a complete environment file with credentials that are distinct from stable: ```text ASSESSOR_POSTGRES_DB=assessor_experimental ASSESSOR_POSTGRES_USER=assessor_experimental ASSESSOR_POSTGRES_PASSWORD=replace-with-a-long-random-experimental-password ``` Do not reuse the stable database name, user, or password for experimental. Secret values must never be printed into an Actions log. Optional AI Overview settings may be added only when the company-managed LiteLLM gateway is approved for the privacy-safe ASSESSOR payload: ```text ASSESSOR_AI_MODE=litellm ASSESSOR_AI_BASE_URL=https://work-litellm.example/v1 ASSESSOR_AI_MODEL=approved-model-alias ASSESSOR_AI_API_KEY= ASSESSOR_AI_TIMEOUT_MS=60000 ASSESSOR_AI_CHUNK_CHARS=80000 ASSESSOR_AI_MAX_OUTPUT_BYTES=262144 ASSESSOR_AI_MAX_CALLS=64 ASSESSOR_AI_JOB_LEASE_MS=300000 ``` Do not place these values in the repository or expose them through client-side configuration. The workstation uses the ignored `.env.postgres`; staging and production use their existing complete environment-file secrets. See [`AI_OVERVIEW_SECURITY.md`](AI_OVERVIEW_SECURITY.md) for the transmitted-data and trust-boundary contract. To upload the licensed DIN ISO 27001 and 27002 PDFs from Data Hub, add the following values to the environment's `STAGING_ENV_FILE` or `PROD_ENV_FILE`: ```text ASSESSOR_QDRANT_API_KEY= ASSESSOR_RAG_EMBEDDING_BASE_URL=https://approved-embedding-gateway.example/v1 ASSESSOR_RAG_EMBEDDING_MODEL= ASSESSOR_RAG_EMBEDDING_API_KEY= ASSESSOR_RAG_EMBEDDING_DIMENSIONS=1024 ASSESSOR_ISO_UPLOAD_TOKEN= ``` The operator enters the upload token in Data Hub; it is checked by the backend before either PDF is read and is not saved in browser storage. Keep the LiteLLM settings above as well to enable mapping after indexing. The upload stores the generated corpus privately in the environment's artifact volume, deletes temporary original PDFs after parsing, and activates a new version only after successful embedding. No server shell or manual corpus-version environment edit is needed for this workflow. Use an embedding destination approved for the licensed ISO text. The token protects only this upload action; it does not add general user authentication. ## Mandatory server inventory Before the first push-triggered deployment, connect to the target server over SSH and perform a read-only inventory. Record only technical identifiers; do not inspect database rows, uploaded files, application pages, or report text. ```sh docker version docker compose version docker ps -a --format '{{.Names}}\t{{.Image}}\t{{.Ports}}\t{{.Label "com.docker.compose.project"}}' docker volume ls docker network ls docker ps --filter publish=4173 --format '{{.Names}}\t{{.Ports}}' docker ps --filter publish=4174 --format '{{.Names}}\t{{.Ports}}' ``` Also identify the existing reverse proxy and firewall policy without changing them. Confirm that `10.0.0.154` is a host address, port 4174 is unused, the runner uses this Docker daemon, and `assessor_experimental` does not belong to an unrelated stack. Port 4173 and project `assessor_prod` are intentionally adopted as stable; only its expected `app`, `postgres`, and `qdrant` services may exist. Any other conflict blocks deployment and is never resolved by stopping or deleting the existing resource. ## Deployment behavior Pushes to a branch are serialized per environment with an atomic lock container on the shared Docker host. This is necessary because Gitea currently ignores the GitHub-compatible workflow `concurrency` key. A normal job removes its lock; a crashed job's `--rm` lock expires after two hours. The workflow installs dependencies, runs the complete test suite and production build, validates the resolved Compose configuration, checks port/project isolation, backs up an existing target environment, then builds and starts only that environment. Compose project scoping produces independent PostgreSQL, artifact, report-archive and Qdrant volumes. The Report Engine (2.10.0) archives every report run write-once under `ASSESSOR_REPORT_ARCHIVE_DIR` (`/var/lib/assessor/report-archive` in Compose). The archive holds complete technical customer data, is deleted together with its client and must be included in backup and retention decisions like the artifact volume. The Report Engine reads PostgreSQL through its own pool with `default_transaction_read_only=on`; it runs no migrations and writes nothing to the database. PostgreSQL and Qdrant have no host-published ports. Qdrant is an authenticated, rebuildable regulatory index; its volume improves restart time but is not a system-of-record backup. `restart: unless-stopped` preserves both environments across Docker daemon and server restarts. Feature promotion is a merge from `experimental` to `main`. Stable is rebuilt from the merged commit; databases and artifacts are never copied between the environments. ## Backups and restore checks Before every update of an existing environment, the workflow writes a custom PostgreSQL dump, an artifact archive and SHA-256 checksums to the external Docker volume `assessor_deployment_backups`: ```text //postgres.dump //artifacts.tar.gz //SHA256SUMS ``` The workflow never deletes backups. Retention and off-host copying require a separate approved operations policy. The backup volume must be included in the server's protected backup system; a volume on the same Docker host is not a complete disaster-recovery copy. Restore tests must use an isolated temporary Compose project and synthetic data. Verify `SHA256SUMS`, restore `postgres.dump` with `pg_restore --exit-on-error` into a fresh database, extract the artifact archive into a fresh artifact volume, and confirm only content-free counts and health state. Never restore an experimental backup into stable or a stable backup into experimental. Never run `docker compose down --volumes` for either permanent environment. ## Acceptance and later cutover After deployment, verify both in-container health checks and VPN access. Import different synthetic fixtures into stable and experimental, redeploy each branch, and confirm persistence and isolation. Confirm the old HTTPS site remains available without reading its domain content. Changing `assessor.soc-ops.de` is not part of the deployment workflow. After explicit production acceptance, validate the existing proxy configuration, preserve its old upstream, point it to stable, reload it, and verify TLS and health. Rollback restores the saved upstream; the archived old stack and its data must remain recoverable until a later retention decision.