Skip to content
ConfigurationServer

Server

You can adjust the server configuration with the following environment variables.

Variables

Env varRequiredDescriptionSupported valuesDefault value when missing
DD_VERSIONRuntime version metadata shown in the API, debug dump, startup banner, and self-update checks. Official images set this at build time; override only for custom builds or tests.Non-empty string (unknown is ignored)package version, then unknown
DD_PUBLIC_URLPublic-facing URL for absolute links and OIDC callbacks. Required when any OIDC authentication provider is configured; other links fall back to request-based detection when unset.URL (e.g., https://drydock.example.com)auto-detected for non-OIDC request links
DD_SERVER_NAMEServer name used in notifications to identify this instance (useful in multi-server setups with agents)Any stringos.hostname()
DD_SERVER_ENABLEDIf REST API must be exposedtrue, falsetrue
DD_SERVER_UI_ENABLEDServe the web UI (set to false for headless/API-only mode)true, falsetrue
DD_SERVER_PORTHttp listener portfrom 0 to 655353000
DD_SERVER_TRUSTPROXYTrust X-Forwarded-For headers when behind a reverse proxytrue, false, or hop count (1, 2, etc.)false
DD_SERVER_TLS_ENABLEDEnable HTTPS+TLStrue, falsefalse
DD_SERVER_TLS_KEYTLS server key (required when DD_SERVER_TLS_ENABLED is enabled)File path to the key file
DD_SERVER_TLS_CERTTLS server certificate (required when DD_SERVER_TLS_ENABLED is enabled)File path to the cert file
DD_SERVER_CORS_ENABLEDEnable CORS Requeststrue, falsefalse
DD_SERVER_CORS_ORIGIN🔴 (when CORS enabled)Allowed CORS origin — required when DD_SERVER_CORS_ENABLED=true; startup will abort if CORS is enabled but this is unset* or a single origin URL (for example https://drydock.example.com)
DD_SERVER_CORS_METHODSSupported CORS methodsComma separated list of valid HTTP verbsGET,HEAD,PUT,PATCH,POST,DELETE
DD_COMPAT_WUDCARDMount the opt-in wud-card compatibility endpoints at the unversioned /api/* pathtrue, falsefalse
DD_SERVER_COMPRESSION_ENABLEDEnable gzip response compression (SSE responses are excluded automatically)true, falsetrue
DD_SERVER_COMPRESSION_THRESHOLDMinimum response size in bytes before compression is appliedinteger (>=0)1024
DD_SERVER_FEATURE_CONTAINERACTIONSEnable start, stop, restart, and update actions via API and UItrue, falsetrue
DD_SERVER_FEATURE_DELETEIf deleting operations are enabled through API & UItrue, falsetrue
DD_SERVER_METRICS_AUTHRequire authentication on /metrics endpointtrue, falsetrue
DD_SERVER_METRICS_TOKENBearer token for /metrics Prometheus scraping (takes priority over session auth when set)String (minimum 16 characters)
DD_SESSION_SECRETOverride the auto-generated, persisted session secret used for cookie signingAny non-empty string (whitespace-only values are ignored)auto-generated and persisted to /store on first boot
DD_SERVER_COOKIE_SAMESITESession cookie SameSite policy for auth flows (none requires HTTPS)strict, lax, nonelax
DD_SERVER_SESSION_MAXCONCURRENTSESSIONSMaximum concurrent authenticated sessions per user (oldest sessions are revoked first at login when limit is reached)integer (>=1)5
DD_SERVER_RATELIMIT_IDENTITYKEYINGKey authenticated-route rate limits by session/username instead of IP (prevents collisions for multiple users behind shared proxies)true, falsefalse
DD_AUTH_LOCKOUT_MAX_TRACKED_IDENTITIESMaximum number of unique client identities held in the auth lockout state. Older entries are pruned when the cap is reached.integer (>0)5000
DD_AUTH_LOCKOUT_PRUNE_INTERVAL_MSInterval for pruning expired auth lockout entries from memory and the persisted lockout sidecar fileinteger (>0)60000
DD_AUDIT_UPDATE_AVAILABLE_DEDUPE_MSSuppress repeated update-available audit rows for the same agent, watcher, container, and version transition. A no-update report or a changed target starts a new transition immediately. Set to 0 to disable deduplication.integer (>=0, milliseconds)3600000 (1 hour)
DD_SSE_MAX_CLIENTSMaximum total concurrent SSE connections across all sessions (global cap; individual sessions are also capped at 10). Connections beyond this limit receive a 429 Too Many Requests response.integer (>0)500
DD_RUN_AS_ROOTRequest break-glass root mode (requires DD_ALLOW_INSECURE_ROOT=true)true, falsefalse
DD_ALLOW_INSECURE_ROOTExplicit acknowledgment for break-glass root modetrue, falsefalse

For log output configuration (DD_LOG_LEVEL, DD_LOG_FORMAT), see Logs configuration.

CORS Security Guidance

DD_SERVER_CORS_ORIGIN is required when DD_SERVER_CORS_ENABLED=true. Drydock will abort at startup if CORS is enabled without an explicit origin. To intentionally allow all origins (local testing only), set DD_SERVER_CORS_ORIGIN=* explicitly.

For production deployments, set a specific trusted origin:

  • DD_SERVER_CORS_ORIGIN=https://drydock.example.com
  • DD_SERVER_CORS_ORIGIN=https://ops.example.com

wud-card Compatibility

The unversioned /api/* alias was removed in v1.6.0 — see Deprecations. Setting DD_COMPAT_WUDCARD=true opts back in to a narrow slice of that surface: the four endpoints the Home Assistant wud-card integration calls. Three of them (the container list, per-container triggers list, and watch endpoints) are reshaped into the bare-array response that card expects instead of drydock's { data, total, limit, offset, hasMore, _links } envelope; the fourth (running a trigger) already returns a small non-enveloped body and passes through unmodified. These endpoints stay mounted at /api/* even after the unversioned alias itself is removed, served by the compat router's own internal API router instance rather than by falling through to the removed alias. It exists purely to keep wud-card working against drydock and is off by default. Every other route under /api/* is unaffected by this flag; most of them now return 410 Gone, except two standalone auth aliases — GET /api/auth/methods and GET /api/auth/status — that keep responding unconditionally because they're registered directly on the app rather than through the removed alias router. See Deprecations for details.

The same flag also covers Homepage's native whatsupdocker widget: Homepage's dashboard GETs /api/containers expecting the same bare array wud-card does, and proxies the request server-side, so no CORS configuration is needed for that integration.

This is a best-effort compatibility shim, not a supported integration surface — it carries no compatibility guarantee and may change or be removed without the usual deprecation window. Requests through it are authenticated and rate-limited exactly like the rest of the API; there is no security bypass.

If Home Assistant runs on a different origin than drydock, pair DD_COMPAT_WUDCARD=true with the CORS settings above: set DD_SERVER_CORS_ENABLED=true and DD_SERVER_CORS_ORIGIN to the Home Assistant frontend's origin (for example https://homeassistant.example.com), not drydock's own address. CORS headers alone do not make session-cookie login work across origins — drydock's CORS middleware never sends Access-Control-Allow-Credentials, and wud-card's fetch() calls never request credentials either, so the session cookie is never sent cross-origin. The instance must be reachable either with no authentication configured or via HTTP Basic auth, the only auth mode that actually completes cross-origin. wud-card also offers a Bearer-token auth option, but drydock has no static API token equivalent yet, so that option will silently fail — configure wud-card for Basic auth (or an auth-less instance) instead.

Container Healthcheck

The official Docker image includes a built-in HEALTHCHECK that polls the /health endpoint using a lightweight static binary. When DD_SERVER_TLS_ENABLED=true, the healthcheck automatically switches to HTTPS (with --insecure semantics for self-signed certificates). No additional configuration is needed — remove any custom healthcheck: blocks from your compose file.

Deprecated: Custom curl-based healthcheck overrides (e.g. test: curl --fail http://localhost:3000/health) remain supported for backward compatibility in v1.5.x. When Drydock can inspect its own container through the local Docker socket, the UI shows a deprecation banner if it detects a curl-based override. v1.6.0 is the final warning release, and curl will be removed from the image in v1.7.0. Use the built-in probe or switch to /bin/healthcheck ${DD_SERVER_PORT:-3000} for custom intervals.

Plain HTTP Deployments

When DD_SERVER_TLS_ENABLED is not set or is false, drydock automatically adjusts its security headers for plain HTTP:

  • HSTS is omitted (since the browser is not on HTTPS)
  • upgrade-insecure-requests CSP directive is omitted (prevents browsers from blocking sub-resource loads)

No additional configuration is required — drydock detects the TLS state and adapts automatically. If you run drydock behind a TLS-terminating reverse proxy, set DD_SERVER_TRUSTPROXY=1 (use a hop count, not true, to avoid IP spoofing — see the Reverse proxy section below) so drydock sees the correct protocol from X-Forwarded-Proto.

  • Use lax (default) for typical web + OIDC setups.
  • Use strict only when drydock and IdP are same-site and you want the strictest cookie policy.
  • Use none only when you explicitly need cross-site cookies (for example embedded UI), and only over HTTPS. Setting DD_SERVER_COOKIE_SAMESITE=none causes a startup validation check -- drydock will refuse to start unless DD_SERVER_TLS_ENABLED=true or DD_SERVER_TRUSTPROXY is set, ensuring cookies are only sent over a secure transport.

Concurrent Session Guidance

DD_SERVER_SESSION_MAXCONCURRENTSESSIONS controls how many authenticated sessions each user may keep active at once. When a login would exceed the cap, drydock revokes that user's oldest existing sessions first.

Examples

Disable http listener

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
      - DD_SERVER_ENABLED=false
docker run \
  -e DD_SERVER_ENABLED=false \
  ...
  codeswhat/drydock

Set http listener port to 8080

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
      - DD_SERVER_PORT=8080
docker run \
  -e DD_SERVER_PORT=8080 \
  ...
  codeswhat/drydock

Enable HTTPS

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
      - DD_SERVER_TLS_ENABLED=true
      - DD_SERVER_TLS_KEY=/drydock_certs/server.key
      - DD_SERVER_TLS_CERT=/drydock_certs/server.crt
docker run \
  -e "DD_SERVER_TLS_ENABLED=true" \
  -e "DD_SERVER_TLS_KEY=/drydock_certs/server.key" \
  -e "DD_SERVER_TLS_CERT=/drydock_certs/server.crt" \
  ...
  codeswhat/drydock

Reverse proxy (trust proxy)

Required when drydock runs behind a TLS-terminating reverse proxy (Traefik, Nginx, Caddy, HAProxy, etc.). Without this setting, drydock cannot determine the real protocol or client IP from forwarded headers, which causes:

  • CSRF validation failures (403 errors on POST/PUT/DELETE requests) — drydock sees http:// internally while the browser sends Origin: https://...
  • Incorrect security headers — HSTS and secure cookie flags may not be applied
  • Wrong client IPs in logs and rate limiting — drydock sees the proxy's IP instead of the real client

Set DD_SERVER_TRUSTPROXY=1 to trust one proxy hop. Use a higher number if you have chained proxies (e.g. CDN → load balancer → app proxy = 3).

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
      - DD_SERVER_TRUSTPROXY=1
docker run \
  -e DD_SERVER_TRUSTPROXY=1 \
  ...
  codeswhat/drydock

Your proxy must forward the X-Forwarded-Proto header so drydock knows whether the original request used HTTPS. Most proxies (Traefik, Caddy, NGINX Proxy Manager) do this by default. For Nginx, add proxy_set_header X-Forwarded-Proto $scheme; to your location block. See the FAQ for complete Traefik and Nginx examples.

Reverse proxy caching (blank page after an update)

After you upgrade drydock, the dashboard can come up as a blank white page on some devices (a phone is the classic case) while a desktop hitting the same reverse proxy loads fine. A hard refresh or clearing the site's data on the affected device fixes it.

Here's why: drydock's UI is a single-page app, and its JS/CSS bundles are content-hashed, so the filenames change with every release and the previous release's files are removed from the image. drydock serves the HTML document itself with Cache-Control: no-store, and serves the hashed files under /assets/ with a long-lived immutable cache, which is correct and intentional. If something ignores no-store and hands back a stale copy of the HTML, that page references bundle files that no longer exist, so the browser has nothing to run and you get a blank page. Most reverse proxies don't cache responses unless you've explicitly turned caching on, so the usual culprits are either a proxy cache you enabled, or the device just holding on to an old copy.

Immediate recovery: clear the browser's site data (or hard-reload) on the affected device. If you've added drydock to a phone's home screen, remove it and re-add it. If your proxy has caching enabled, purge its cache too.

Prevention: make sure whatever sits in front of drydock doesn't cache the HTML document, or that it honors the upstream Cache-Control: no-store. Caching /assets/* is fine, in fact it's the point, those filenames are unique per build so they're safe to cache forever.

If you've turned on proxy_cache for drydock, exclude the HTML document from it. The simplest fix is to not enable caching on drydock's location at all and let Nginx forward drydock's own Cache-Control header, which it does by default:

location / {
    proxy_pass http://drydock:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    # no proxy_cache directive here — drydock's own
    # Cache-Control headers control caching
}

Caddy's reverse_proxy doesn't cache responses on its own, so there's nothing to configure. If you've added a caching plugin or directive in front of drydock, exclude Content-Type: text/html from it.

Traefik doesn't cache HTTP responses by default either, so no action is needed out of the box. If you've added a caching middleware or plugin, exclude the HTML document from it there too.

drydock already sends Cache-Control: no-store on the HTML document, and as of v1.6 returns a clean 404 for a hashed asset that no longer exists, so a stale page fails fast instead of rendering a blank screen. A caching layer that overrides no-store can still serve a stale page though, which is why this ultimately comes down to your proxy's (or device's) cache configuration.

Advanced Tuning

These variables control outbound HTTP calls, the icon proxy cache used to serve container icons, and fleet-stat aggregation. Defaults are suitable for most deployments.

Env varRequiredDescriptionSupported valuesDefault value when missing
DD_OUTBOUND_HTTP_TIMEOUT_MSDefault timeout for outbound registry, trigger, and provider HTTP requests that do not set a narrower provider-specific timeoutinteger (>0)30000
DD_ICON_CACHE_TTL_MSTTL for cached icon files on diskinteger (ms)2592000000 (30 days)
DD_ICON_CACHE_MAX_FILESMaximum number of icon files in cacheinteger (>0)5000
DD_ICON_CACHE_MAX_BYTESMaximum total size of icon cache in bytesinteger (>0)104857600 (100 MB)
DD_ICON_CACHE_ENFORCEMENT_INTERVAL_MSInterval for enforcing icon cache file/count/byte limitsinteger (>0)10000
DD_ICON_IN_FLIGHT_TIMEOUT_MSTimeout for coalesced in-flight icon fetches before another request may retryinteger (>0)15000
DD_ICON_PROXY_RATE_LIMIT_WINDOW_MSSliding window for icon proxy request rate limitinginteger (>0)60000
DD_ICON_PROXY_RATE_LIMIT_MAXMaximum icon proxy requests allowed per rate-limit windowinteger (>0)100
DD_STATS_INTERVALStats aggregator poll cycle in seconds — how often the fleet-stats snapshot is refreshedinteger (>0)10
DD_STATS_HISTORY_SIZENumber of historical fleet-stat snapshots retained per container (older snapshots are evicted when the cap is exceeded)integer (>0)60

Secrets from files

Any DD_* environment variable can be loaded from a file by appending __FILE to the variable name and setting the value to the file path. This is useful for Docker secrets and other secret management tools.

services:
  drydock:
    image: codeswhat/drydock
    ...
    environment:
      - DD_SERVER_WEBHOOK_TOKEN__FILE=/run/secrets/webhook_token
      - DD_SESSION_SECRET__FILE=/run/secrets/session_secret
    secrets:
      - webhook_token
      - session_secret

secrets:
  webhook_token:
    file: ./webhook_token.txt
  session_secret:
    file: ./session_secret.txt
docker run \
  -e DD_SERVER_WEBHOOK_TOKEN__FILE=/run/secrets/webhook_token \
  -e DD_SESSION_SECRET__FILE=/run/secrets/session_secret \
  -v ./webhook_token.txt:/run/secrets/webhook_token:ro \
  -v ./session_secret.txt:/run/secrets/session_secret:ro \
  ...
  codeswhat/drydock

File-sourced secrets follow the common Docker *_FILE convention:

  • If both DD_FOO and DD_FOO__FILE are set, the file value wins.
  • Trailing whitespace is trimmed, so secrets created with echo or an editor do not include accidental trailing newlines.
  • On Unix-like systems, Drydock logs a non-fatal warning when a secret file is readable by group or others. Use chmod 600 <secret-file> where possible. The permission check is skipped on Windows because POSIX mode bits are not meaningful there.
  • DD_SESSION_SECRET__FILE is supported and uses the same rules as other DD_*__FILE secrets.