Skip to content
English

Production deployment

Turning a trial deployment into one you can hand over: the values you have to fill in, the security floor that applies by default, the checks to finish before go-live, and a set of verification commands you can run one by one. The default docker-compose.yml produces the production build, where nginx serves the compiled frontend, the backend is a slim binary image, and the database and graphics proxy publish no external ports.

  • A Linux x86_64 or aarch64 host that can run Docker and Docker Compose, and storage planned for DATA_PATH.
  • A public host name and a decision about where the certificate comes from. The system signs one itself and serves https by default; for an organizational certificate or your own ingress, see TLS and certificates.
  • An off-host syslog collector, configured as described in Log forwarding and notifications.
  • No software on the target hosts. Control happens on the gateway the connection passes through.

For a package deployment, verify the release SHA-256 list and Sigstore data, then run custodexa.sh install. Images can come from the architecture-specific offline bundle or, in order, local images, GHCR, and Docker Hub, with verification; a source build is also available. See Release package and offline installation.

Variable Notes
JWT_SECRET The production build refuses to start when it finds the template default or a value that is too short; at least 32 bytes
KEK_PROVIDER Master key mode, shipped as ui, with env and kms as alternatives
ENCRYPTION_KEY Only filled in when KEK_PROVIDER=env
DB_PASSWORD The production build ships no default; required
ADMIN_INITIAL_PASSWORD Required on a fresh deployment; at least 12 bytes and no whitespace
DATA_PATH Point it at a dedicated folder or disk; use an absolute path in production
DB_SSLMODE Set to require or verify-full when the database is on another host
CORS_ALLOWED_ORIGINS Filled in for cross-origin deployments; empty means same-origin only
TLS_DOMAIN The public host name; it goes into the certificate and into the proxy configuration
TLS_MODE Where the certificate comes from; ships as selfsigned, set to provided for your own
TRUSTED_PROXIES Filled in when deployed behind an ingress of your own; it decides the source address in audit records and the basis for rate limiting
PUBLIC_BASE_URL The external https address, on the same host name as TLS_DOMAIN; the OIDC callback address is built from it

The constants of the service topology (ports, database host, where data lands) come from compose, so you do not fill them in yourself.

These behaviors are built into the deployment and have no off switch:

  • External connections run over https by default: the built-in proxy provides TLS 1.2 or later, HSTS and WebSocket forwarding, and redirects plain http.
  • There is no built-in public initial credential. The deployer sets the initial administrator password, and the first login forces a change.
  • The production build refuses to start when the JWT secret is still the template default or too short.
  • Sensitive data at rest uses layered envelope encryption, with a key inventory and rekey governance.
  • The production image removes the fixed shell entry point, and the database and graphics proxy publish no external ports.
  • The database schema is defined by a single baseline. An existing version it does not recognize makes it refuse to start rather than run anyway.
  1. Apply the in-force policy groups: on the security policy page and the access control page, pick a group at the page head, read the preview, then confirm. A stricter key is left alone, and a conflict between groups is left to the administrator. It takes effect when you save.
  2. Set up the off-host syslog collector and confirm a test message really arrives.
  3. Check the directory permissions on DATA_PATH. Use 0750 or stricter, owned by the user the container runs as.
  4. Replace every secret that came from the template. Once the database has finished initializing, remove ADMIN_INITIAL_PASSWORD from .env.
  5. Establish a backup procedure and record the four fingerprints on the key inventory.
  6. Set up the login banner, with wording your organization chooses.
  7. Before you turn on single sign-on, keep at least one administrator account that signs in with a local password. Give it a strong password and enable two-factor authentication.
  8. Install a certificate from an organizational or public CA, or distribute the certificate authority the system signed to the machines that connect.
Terminal window
docker compose ps
docker compose exec backend wget -qO- http://localhost:8080/health
curl -skI https://localhost/ | head -1
curl -sk -X POST https://localhost/api/v1/auth/login -H "Content-Type: application/json" \
-d '{"username":"admin","password":"<ADMIN_INITIAL_PASSWORD>"}'
docker compose logs backend | tail -30

On a fresh deployment the login response carries password_change_required, which means the forced password change is in effect. When verifying the production build on a development machine, add -f docker-compose.yml to every command so it points at the production configuration.

  • Every change to a security policy leaves a record with the person who made it, the policy key, and the values before and after.
  • Login events record the account, source address, and result. The source address is the real origin of the connection once TRUSTED_PROXIES is set.
  • Deployment shape, backup scope, and upgrade steps are operational evidence; see Backup and restore and Upgrade and rollback.