Skip to content
ConfigurationAuthentication

Authentication

drydock requires authentication by default on fresh installs.

Since v1.4.0, drydock requires authentication on fresh installs. If no auth strategy is configured and no explicit anonymous opt-in is present, protected API requests return 401 Unauthorized; public auth discovery/status routes remain available so the login surface can explain the problem.

You can enable 1 or multiple authentication strategies using DD_AUTH_* env vars.

Authentication protects protected API routes and UI data unless anonymous access is enabled. Fresh installs and upgrades alike must opt in with DD_ANONYMOUS_AUTH_CONFIRM=true — without it, an instance with no auth configured starts fail-closed. Protected API requests return 401, auth discovery/status remains public, and /health returns 503. The SPA shell may still load, but it cannot read protected application data.
For OIDC setups, session-cookie policy is controlled by DD_SERVER_COOKIE_SAMESITE in server configuration. Default lax works for most IdP callback flows.

Login lockout controls

To limit brute-force attempts on POST /auth/login, drydock tracks failed logins per account and per client IP and can temporarily lock access when thresholds are exceeded. Per-account controls use DD_AUTH_ACCOUNT_LOCKOUT_*; per-IP controls use DD_AUTH_IP_LOCKOUT_*.

Env varRequiredDescriptionSupported valuesDefault value when missing
DD_AUTH_ACCOUNT_LOCKOUT_MAX_ATTEMPTS⚪Max failed login attempts per account before temporary lockoutPositive integer (> 0)5
DD_AUTH_IP_LOCKOUT_MAX_ATTEMPTS⚪Max failed login attempts per client IP before temporary lockoutPositive integer (> 0)25
DD_AUTH_LOCKOUT_WINDOW_MS⚪Rolling window (milliseconds) used to count failed login attemptsPositive integer (> 0)900000
DD_AUTH_LOCKOUT_DURATION_MS⚪Lockout duration (milliseconds) once a threshold is reachedPositive integer (> 0)900000
DD_AUTH_LOCKOUT_PRUNE_INTERVAL_MS⚪Interval for pruning expired lockout entries from memory and the persisted lockout sidecar filePositive integer (> 0)60000
DD_AUTH_LOCKOUT_MAX_TRACKED_IDENTITIES⚪Maximum number of unique account/IP identities tracked before older entries are prunedPositive integer (> 0)5000
DD_AUTH_MAX_CONCURRENT_LOGIN_ATTEMPTS⚪Maximum login credential verifications running at once. Excess attempts receive 429 with Retry-After: 1 before password hashing starts.Positive integer (> 0)2

If a lockout value is missing or invalid, drydock falls back to the corresponding default.

Two-factor (TOTP)

Local Basic accounts can add a TOTP second factor through the two-factor management API. Enrolling, replacing or removing a factor is only allowed over HTTPS or from loopback, because those responses carry the TOTP secret and the recovery codes.

Env varRequiredDescriptionSupported valuesDefault value when missing
DD_AUTH_TOTP_KEYRING__FILE⚪Path to the key ring file that encrypts the stored TOTP secrets. Nobody can enroll without itPath to a readable filenone
DD_AUTH_TOTP_ACTIVE_KEY_ID⚪Id of the key ring key new secrets are encrypted under. Set it whenever the key ring is setA key id from the key ringnone
DD_AUTH_TOTP_ALLOWHTTP⚪Allow two-factor enrollment and management over plain HTTP from any addresstrue, falsefalse
With DD_AUTH_TOTP_ALLOWHTTP=true, the TOTP secret, the recovery codes and the password sent with each management call cross the network unencrypted. Anyone who can read that traffic can copy the second factor as it's being set up, which leaves the account with no second factor in practice. Drydock logs a warning at startup while it's on. Prefer HTTPS, or DD_SERVER_TRUSTPROXY behind a TLS-terminating proxy, and leave this unset.

DD_AUTH_TOTP_ALLOWHTTP is read at startup. Any value other than true or false is a configuration error and Drydock won't start. In drydock.yml it's auth.totp.allowhttp.

The key ring format, key rotation, recovering an account that lost its device or its recovery codes, what a lost key ring means, and what happens when an enrolled account is renamed are on the two-factor key ring and recovery page.

Currently, the following strategies are supported:

Anonymous access

On previous versions of drydock (and upstream WUD), the dashboard was accessible without any login. Starting with v1.4.0, anonymous access must be explicitly opted into on fresh installs.

DD_ANONYMOUS_AUTH_CONFIRM

Set DD_ANONYMOUS_AUTH_CONFIRM=true to explicitly allow anonymous (unauthenticated) access to the dashboard and API.

DD_AUTH_ANONYMOUS_CONFIRM=true is also accepted as an alias. DD_ANONYMOUS_AUTH_CONFIRM is the canonical name.

environment:
  - DD_ANONYMOUS_AUTH_CONFIRM=true

Behavior matrix

ScenarioResult
Fresh install, no auth configured, no DD_ANONYMOUS_AUTH_CONFIRMStarts fail-closed — protected API requests return 401; auth discovery/status stays public; /health returns 503
Fresh install, DD_ANONYMOUS_AUTH_CONFIRM=trueAnonymous access allowed
Upgrade, no auth configured, no DD_ANONYMOUS_AUTH_CONFIRMStarts fail-closed — protected API requests return 401; auth discovery/status stays public; /health returns 503
Upgrade, DD_ANONYMOUS_AUTH_CONFIRM=trueAnonymous access allowed

Drydock identifies an existing installation from its saved application metadata. In v1.8.0-rc.1 builds this record lives in SQLite and is carried across by the one-time JSON import; the presence of dd.json alone does not determine whether an installation is an upgrade. Both upgrades and fresh installs require configured authentication or explicit anonymous-access confirmation.

Upgrading

Upgrades fail closed exactly like fresh installs — there is no more warn-and-continue grace period. If an upgrading instance has no authentication configured (or anonymous auth is enabled but not explicitly confirmed), the container runs while protected API requests return 401, auth discovery/status remains public, and /health returns 503. The SPA shell may still load, but it cannot read protected application data. This replaces the previous warning plus open dashboard.

To restore access, either:

  1. Configure authentication — set DD_AUTH_BASIC_<name>_USER and DD_AUTH_BASIC_<name>_HASH (or use OIDC).
  2. Explicitly opt in to anonymous access — set DD_ANONYMOUS_AUTH_CONFIRM=true.
v1.6.0-rc.3 renamed the session cookie (connect.sid → drydock.sid). Upgrading from a build that still used the old name signs out existing sessions once, Basic and OIDC alike; users log back in. This earlier cookie change is separate from the v1.8 SQLite import.

Fail-closed auth enforcement

Since v1.4.0, drydock enforces fail-closed authentication across registries, triggers, and the Docker entrypoint. If auth is configured and fails, drydock raises an error instead of silently falling back to anonymous or unauthenticated access.

This affects three areas:

  • Registry bearer-token flows — When a token exchange endpoint returns an error (network failure, invalid credentials, expired token), the registry call fails immediately. In older versions (and upstream WUD), this would silently fall through to anonymous access, which could cause missed updates on private images or confusing inconsistencies between authenticated and unauthenticated results.
  • HTTP trigger auth — If an HTTP trigger specifies an unsupported auth type, the trigger errors instead of sending an unauthenticated request.
  • Docker entrypoint root mode — Running as root requires both DD_RUN_AS_ROOT=true and DD_ALLOW_INSECURE_ROOT=true. Setting only one flag fails closed at startup. See Docker Socket Security.

If you are upgrading from WUD or an earlier drydock version and see new auth errors, verify that your credentials are valid and that outbound HTTPS to the token endpoint is not blocked. The errors are intentional — they surface problems that were previously hidden.

Remember me

The login form includes a Remember me checkbox. When checked, the session cookie is extended to 30 days so the browser stays logged in across restarts. When unchecked, the cookie expires when the browser is closed (default session lifetime is 7 days).

Remember-me works with both Basic and OIDC authentication strategies. The preference is stored server-side in the authenticated session via POST /auth/remember.