Configuration and hardening
All settings are environment variables. There is no configuration file. An invalid value makes the server exit at startup with status 1.
Essential variables
| Variable | Example | Why |
|---|---|---|
GITLAB_ISSUER | https://gitlab.example.com | Required. The server fetches the token keys from it, over HTTPS only |
GITLAB_AUDIENCE | https://deploy.example.com | Required. Must equal aud in the jobs' id_tokens |
ALLOWED_NAMESPACES | apps/atmosphere,infra | Only these groups and their sub-groups may deploy. Empty = every project of the instance |
ALLOWED_IPS | CI runner egress IPs | Other clients get 403. Empty = disabled |
TRUSTED_PROXIES | 172.31.250.2 | Traefik's exact IP, so the client IP is read from X-Forwarded-For |
FORBIDDEN_TRAEFIK_HOSTS | deploy.example.com,gitlab.example.com | Hosts no stack may route |
ALLOWED_EXTERNAL_NETWORKS | web | The only external network stacks may join |
PROTECTED_ENVS | production (default) | These environments require a protected branch or tag |
Every variable, default and validation: Configuration reference.
Hardening checklist
These are preconditions of the security model, not recommendations.
- Private GitLab. Anyone who can run a pipeline under an allowed namespace can deploy containers on the host. With open sign-up, every user owns a namespace.
ALLOWED_NAMESPACESis set. Empty, it accepts every project of the instance (a warning is logged at startup).- Legacy stacks are imported with
-import-registrybefore pipelines can call the server. Otherwise the first deploy that reaches an unclaimed key takes it over. See Migrate from SSH deployment. - Legacy containers are visible or gone. The server only finds containers labelled
com.docker.compose.project=<key>or named<key>-…/<key>_…. Stop the others by hand before the stack's first deploy. - Only the reverse proxy reaches the server.
:8080is never published, and deploy-server is on no network that stacks can join. - Traefik is isolated from stacks. Stacks reach it only through
ALLOWED_EXTERNAL_NETWORKS. The deploy route is in the file provider, on:8443, towards a fixed IP; Traefik uses a socket proxy,exposedByDefault=false, no dashboard or API. Firewall:8443to the CI runners. TRUSTED_PROXIESis exact. Only the proxy's address, never a Docker pool such as172.16.0.0/12: any peer in that range can forge the client IP.ALLOWED_IPSis set to the CI runner egress addresses. It limits who can replay a leaked token.- The server environment is minimal. Only the variables of the configuration reference, plus
PATH. No secrets: compose interpolation can copy any of them into a container. - Host and container paths match for
COMPOSE_BASEandWEBSITES_BASE. - The intermediate levels of
WEBSITES_BASEbelong to root.WEBSITES_BASE,<key>/and<key>/<service>/areroot:root 0755, writable by no other user and not on a shared or network filesystem.
warning
- Never list
deploy-internalorsocket-proxyinALLOWED_EXTERNAL_NETWORKS: a stack could then reach the server directly, or the Docker API. - Never widen
TRUSTED_PROXIESto a network range. - Never put secrets in the server's environment.
Private registries
Mount a docker config.json read-only on /root/.docker/config.json. Compose forwards these credentials to the daemon, so every stack can pull what they grant access to.
Next: Security model.