Fail fast and list the missing tools instead of surfacing unrelated
errors later in the install; a missing nginx, for example, only failed
once plugin:install --core ran. docker compose is accepted either as
the standalone binary or as the docker compose plugin.
debian/postinst creates /var/lib/dokku/data and gives it to the dokku
user, but make install never did. When data ends up root-owned, every
deploy fails because the build hook creates data/builds/<app> as the
dokku user. Mirror the postinst setup and repair root-owned entries
below data as well.
Vendored copy refreshed from dokku/dokku-letsencrypt master (0.20.4 ->
0.25.2), keeping the same set of files the fork vendors (no tests,
Makefile, or CI workflow).
On the target host /tmp is a tmpfs sized at half of RAM (~970M), so the
go module cache and build cache together filled it and every module
download failed with "no space left on device". Move both caches to
/var/tmp, which lives on the root filesystem, and create both cache
directories up front rather than only the build cache.
goproxy.cn intermittently returns HTTP/2 stream errors while fetching
module zips (seen with github.com/dokku/docker-port-forward), which the
default comma-separated list does not fall back from since it only
retries on 404/410. Use the pipe separator so any error falls through to
the aliyun mirror and then direct.
plugins/datastore/go.mod now requires go >= 1.27.3 (dependabot bumped
github.com/dokku/dokku-datastore), but the build image was still 1.27.1,
so the in-docker go builds failed with GOTOOLCHAIN=local.
* origin/master: (333 commits)
chore(deps): bump gunicorn in /tests/apps/dockerfile-release
chore(deps): bump importlib-metadata in /docs/_build
chore(deps): bump soupsieve from 4.0.1 to 4.1 in /docs/_build
chore(deps): bump gunicorn from 26.2.0 to 27.0.0 in /tests/apps/multi
chore(deps): bump gunicorn in /tests/apps/python-flask
chore: bump pack to 0.40.11
chore(deps): bump pymdown-extensions from 12.3 to 13.0 in /docs/_build
chore(deps): bump rack
chore(deps): bump sass from 1.108.0 to 1.108.1 in /tests/apps/multi
chore(deps): bump mkdocs-material from 9.8.0 to 9.8.1 in /docs/_build
chore(deps): bump click from 8.5.0 to 8.5.1 in /docs/_build
chore(deps): bump github.com/dokku/dokku-datastore in /plugins/datastore
chore(deps): bump golang.org/x/sync in /plugins/common
chore(deps): bump sass from 1.107.1 to 1.108.0 in /tests/apps/multi
chore(deps): update markdown requirement in /docs/_build
chore(deps): bump watchdog from 7.0.0 to 7.1.0 in /docs/_build
chore(deps): bump soupsieve from 3.0.4 to 4.0.1 in /docs/_build
chore(deps): bump traefik in /plugins/traefik-vhosts
fix: build plugins/common on windows
chore: bump herokuish to 0.11.25
...
# Conflicts:
# common.mk
# contrib/dependencies.json
The previous release's default Corefile held host port 8080 on every interface, which broke apps and proxies mapping that port. 0.8.0 binds all of its listeners to `127.0.0.1` and moves the health and ready endpoints off common ports.
The minimum version is injected from `contrib/dependencies.json` at package build time, so hardcoding it in `debian/control` produced a duplicate constraint that `dpkg-deb` rejected.
The bundled `nginx.conf.sigil` keeps rendering IP-based upstreams unless `dns-resolver` is set, for example with `dokku nginx:set --global dns-resolver 127.0.0.1:1053`. Unsetting the property reverts to IP-based upstreams, replacing the previous `off` sentinel.
Adds a new `dns-resolver-timeout` global nginx property that controls how long nginx waits on the configured DNS resolver before giving up and returning 502. Without an explicit timeout nginx blocks on the resolver for its built-in 30s default, which means clients see hung requests instead of a fast 502 when coredns-docker is unreachable. The 5s default keeps failure-mode requests short while leaving headroom over a healthy local resolver round trip. The bundled `nginx.conf.sigil` emits `resolver_timeout {{ $.NGINX_DNS_RESOLVER_TIMEOUT }}` next to the existing `resolver` directive, and the outage-mode bats and docker-deploy tests now expect the curl call to complete inside that window.
Adds bats coverage on the bare-metal CI host (stops `coredns-docker` via `systemctl` and asserts the rendered vhost returns 502, then restarts and asserts recovery to 200) and a parallel `tests/test_coredns_outage` driver wired into `make test-ci-docker` that exercises the same flow against the dokku container using `sv stop|start coredns-docker`. Answers the open thread on whether the resolver-mode upstream surfaces a useful error when the DNS service is down.
The dokku deb now declares `coredns-docker (>= 0.6.1)` as a hard `Depends:`, but `tests/ci/setup.sh` uses `dpkg -i` (not `apt-get install`) so transitive deps are not resolved and the install fails. Pre-download the `coredns-docker` deb from packagecloud and include it in the same `dpkg -i` invocation that already installs `docker-container-healthchecker`, `docker-image-labeler`, `lambda-builder`, and the other dokku-maintained deps.
Stops nginx from routing to stale container IPs after host reboots, Docker upgrades without live-restore, or runtime container restarts. The bundled `nginx.conf.sigil` can use an nginx `resolver` directive against the local `coredns-docker` socket and a variable-based `proxy_pass` keyed on `<app>.<process-type>.docker`, so the upstream is resolved at request time honoring DNS TTL. `coredns-docker` becomes a hard `Depends:` of the dokku deb and is bundled in the Docker image under runit. Closes#8313.
Installing from source on an arm64 host downloaded amd64 dependencies and built amd64 plugins, failing once `plugin:install` ran the first binary. The `TARGETARCH` and `GOARCH` make variables now default to the host architecture and remain overridable, and debian packages are labelled with the architecture they were built for. Closes#9217.
Path-based route rules for traefik kept a stray backslash before each backtick, so traefik rejected the router and requests fell through to the web process. The nginx template tests now also supply the route variables and cover route rendering.
Removes the per-step docker inspect label dumps in the caddy/traefik/openresty backend tests, the kubectl get service/ingress/endpoints dumps in the k3s T1 tests, the openresty T2 sidecar log fallback that became unnecessary once 0.12.1 fixed the upstream cache regression, and the trailing cat of /tmp/route-removed-body in every T3. The output/status echoes that match the project's bats convention stay, as does the file-based grep in T3 and every assertion-bearing kubectl call.
The non-web container's caddy=DOMAIN label and web's caddy=DOMAIN:80 label declare separate Caddyfile site blocks, so caddy-docker-proxy never merges the route's handle directive into the site that actually serves port 80. fn-caddy-route-labels now mirrors web's letsencrypt + 443 check and emits DOMAIN:80 in the HTTP-only path. The bats T1 also dumps the web container's labels alongside api's so future declarator mismatches are visible in CI logs.
0.12.0 had a docker-gen template cache regression that suppressed
sidecar reloads when only the `strip-prefix` label was added or
removed on an existing route key. 0.12.1 fixes it, so the two
openresty bats tests previously marked `skip` against
dokku/openresty-docker-proxy#141 can run again.
The k3s-traefik IngressRoute was emitting priority: len(path) on the route rule but no priority on the bare-Host catch-all. Traefik defaults missing priority to rule length, so the catch-all rule got ~32 while the path rule got 7, and the catch-all always won. Same exact priority bug we fixed for docker-traefik. Dropping the priority line lets traefik use its rule-length default which gives longest-prefix-wins naturally because Host && PathPrefix is always longer than bare Host. The caddy web container's emission was a bare reverse_proxy directive that runs at caddy default order ~190, well before the handle directive at ~210 where my route emission lives. The bare reverse_proxy caught every request including paths intended for the route. When at least one route exists for the app, the web container now wraps its reverse_proxy in a catch-all handle block via caddy.9_handle.0_reverse_proxy=...; both the route's handle (at 0_) and web's catch-all (at 9_) live at the same directive order ~210 and caddy's mutually-exclusive handle logic picks the route handle for matched paths and the catch-all for everything else. The "9_" prefix orders web's catch-all after the route handles in the docker-proxy generated Caddyfile. Apps without routes keep the original bare reverse_proxy behaviour.
Caddy T1 now dumps the generated Caddyfile and the adapted JSON from caddy-caddy-1 so the next CI run reveals how caddy-docker-proxy merged the web container's bare reverse_proxy with the api container's handle directive. Without the merged Caddyfile in front of us, we're guessing at caddy's directive evaluation order; with it we can pick the right structural fix. Openresty T2 and T3 are skipped with a reference to dokku/openresty-docker-proxy#141 - the 0.12.0 sidecar's docker-gen template cache observes the label set change but compares the rendered config against its previous output and concludes nothing changed when only the strip-prefix label is added, so proxy_pass never gets the trailing slash that strip-prefix is supposed to add. T1 keeps passing as the smoke that the path-prefix label vocabulary works at all. The k3s tests now run dokku ps:rebuild after every proxy:route:set/remove because scheduler-k3s has no proxy-build-config trigger handler today, so route mutations do not re-render the chart on their own; ps:rebuild forces a redeploy which renders the chart with current routes. The runtime gap (adding a proxy-build-config handler that calls into the chart-render path) is tracked as a follow-up.
Traefik was setting an explicit priority equal to the path length, which is smaller than traefik's default priority (rule length) on the web container. Web's bare Host(...) rule has implicit priority ~40 while my explicit 7 lost the match, so /api/v0/Procfile always went to web. Dropping the explicit priority lets traefik use rule length, which already gives longest-prefix-wins because the route rule includes both Host and PathPrefix and so is always longer than web's bare Host(...). Caddy's route block was being preempted by web's bare reverse_proxy due to caddy's default directive order (reverse_proxy ~190, route ~210). Switching to handle, which terminates matched requests and isn't bypassed by reverse_proxy, makes /api/v0/* land on api instead of web's catch-all. Openresty T2 polls longer (60 retries) and dumps sidecar logs on failure so the upstream label-update cache delay can be reported. The k3s files dump kubectl get service/ingress/endpoints in T1 so the next CI run reveals whether the api Service has ports and whether the Ingress backend reference is correct.
The retry loop in T3 fed the response body through `bash -c "grep <<<\"$body\""`. When the body contains JSON env vars with embedded double quotes (PS1, paths with `$`, etc.), the inner `"$body"` terminates the outer `bash -c "..."` quoting prematurely and bash tries to execute the JSON content as commands. grep itself returns 0 but the subsequent error messages get appended to bats's `$output`, breaking `assert_output "0"`. Each T3 now curls into a file and greps the file directly, so the body never crosses a shell-quote boundary. Each label-based backend file additionally echoes `docker inspect <app>.api.1 --format '{{json .Config.Labels}}'` after `ps:rebuild`, surfacing the actual labels emitted onto the api container so the CI log shows whether the docker-args-process-deploy emission matches what the proxy daemon expects. A 5s sleep after rebuild gives the proxy daemon time to read the new labels before the HTTP retry loop starts.
T3 in every per-backend file polled the post-removal HTTP response using assert_http_localhost_response_contains, which retries on status code only. After a route is removed, both api and web return 200 for /api/v0/Procfile so the polling exits immediately; the body check then races with the proxy daemon picking up the new state and observes api's response (Procfile content) instead of web's catch-all. The proxy:route:remove output was also unechoed, hiding whether BuildConfig actually fired or was no-op suppressed. Each T3 now echoes the remove output and status, asserts via proxy:route:report that storage was cleared, then polls the body in a hand-rolled retry loop until the api marker is no longer present (timeout 30s). The k3s T3 files similarly retry the kubectl probe so the chart re-render has time to settle.
T1 of every per-backend test file asserted 200 with the api Procfile body for a request that lacks --strip-prefix, but without the strip the upstream sees /api/v0/Procfile and python's http.server returns 404; the retry loop polled on status only and accepted web's catch-all 200 first. The assertion now expects 404, which is the right signal that the route reaches the api process. T3 was rewritten to follow T2's two-step pattern (404 without strip, then 200 with strip) so the route is proven applied before checking content. T4 of proxy-route-nginx.bats relaxes the WebSocket header assertions to allow more than one occurrence because the headers correctly appear in both the route location block and the existing location / block. Backticks in fn-traefik-host-rule and fn-traefik-route-labels are now triple-escaped so they survive the docker-args eval path that turns the labels into a docker run invocation, matching the existing convention in docker-args-process-deploy. The label-based backend test files also echo the ps:rebuild output now so future failures of that step surface in the CI logs instead of silently exiting non-zero.
Bumps the openresty sidecar to 0.12.0, which introduces the openresty.route.<n>.* label family for path-based routing. The openresty-vhosts plugin now emits route labels for non-web containers targeted by a proxy:route:set, mirroring the traefik and caddy patterns. Adds integration tests for every rendering backend (proxy-route-{nginx,traefik,caddy,openresty}.bats covering basic routing, --strip-prefix, and removal) plus per-ingress-class scheduler-k3s tests (proxy-route-k3s-{nginx,traefik}.bats covering the same three cases against a real cluster). The nginx file additionally asserts the rendered nginx config carries the WebSocket upgrade headers in each route's location block.
The trigger was added to refuse proxy:route:set under openresty and haproxy, but the hard refusal created an ordering dependency for declarative tools and blocked users from staging routes ahead of a backend switch. Routes are now stored regardless of the active backend and silently ignored by backends that do not render them; the docs call out the inert behavior under openresty and haproxy.
Adds proxy:route:set/remove/clear/report so users can declare path-prefix routes to non-web Procfile processes without hand-writing nginx.conf.sigil. Routes are stored on the proxy plugin as a PropertyMap of path to process:port:strip, with set semantics so each invocation produces a fully-determined state and re-application is idempotent. A proxy-supports-routes capability trigger gates the command per backend - nginx-vhosts, traefik-vhosts, caddy-vhosts, and scheduler-k3s render routes today; openresty-vhosts and haproxy-vhosts refuse until their sidecar images learn a path-prefix vocabulary (tracked in dokku/openresty-docker-proxy#137 and the haproxy sidecar). Closes#8725.
The `stack` property lived on the `buildpacks` plugin but was shared by both the herokuish and pack builders, so a value valid for one builder broke the other. Each builder now owns its own `stack` property managed via `builder-herokuish:set` and `builder-pack:set`, while `buildpacks:set-property stack` is deprecated and forwards the value to the builder detected from it; existing values migrate automatically on upgrade and changing a builder's stack clears that builder's cache.
A catch-all `*` domain or a wildcard ssl hostname could be expanded into file names from the current directory when generating app urls, healthcheck `Host` headers, or the nginx ssl server names.
Caddy healthchecks now honor the `attempts` and `httpHeaders` properties of the `app.json` readiness healthcheck, matching openresty. The `tls-internal` property was also never applied to app containers, and now takes effect for apps served over https.
The caddy, haproxy, openresty, and traefik docs stated that requests are only routed to containers passing healthchecks, but these proxies route to a new container as soon as it starts. The first `web` readiness healthcheck in `app.json` is now also turned into health checks and retries for caddy and openresty, as it already was for traefik, so requests are routed away from containers that are not yet ready, and the docs now describe how each proxy actually behaves.
When a deploy failed, the old containers kept serving while `dokku/<app>:latest` still pointed at the failed image, so `ps:restart`, `ps:start`, and `ps:restore` would redeploy the broken release or fail with `App image not found` once that image was retired. The running image is now kept available during a build and deploy, the `latest` tag is restored to it when the build, release, or deploy fails, and `ps:retire` no longer removes the image an app's `latest` tag points at. Failed containers are also now registered for retirement under the correct app so they and their images are cleaned up.
# History
## 0.38.34
Install/update via the bootstrap script:
```shell
wget -NP . https://dokku.com/install/v0.38.34/bootstrap.sh
sudo DOKKU_TAG=v0.38.34 bash bootstrap.sh
```
### Bug Fixes
- #9223: @josegonzalez Attach every network in attach-post-create
### New Features
- #9225: @josegonzalez Add --no-cache flag to ps:rebuild
The table column style rule added since 0.35.0 is disabled, since the docs pad tables loosely rather than aligning every pipe, and the docs are updated for the other rules it now enforces.
Adds a `datastore` core plugin that serves every official datastore under one command, with the datastore type as the first argument, as in `dokku datastore:create postgres lollipop`. It is built on the dokku-datastore sdk, and leaves a datastore to its official plugin while that plugin is enabled.
The `ps:rebuild` command now accepts `--no-cache` to perform a single uncached build regardless of builder, without persisting any setting for later deploys. The railpack builder now also honors `--no-cache` when set via `docker-options`, and build records are now correctly attributed when flags precede the app name.
Setting multiple networks for the `attach-post-create` network property only connected new app containers to the first network in the list. All specified networks are now attached, matching the existing `attach-post-deploy` behavior.
# History
## 0.38.33
Install/update via the bootstrap script:
```shell
wget -NP . https://dokku.com/install/v0.38.33/bootstrap.sh
sudo DOKKU_TAG=v0.38.33 bash bootstrap.sh
```
### Bug Fixes
- #9221: @fatihesergg Add missing wget dependency to Makefile for source installs
### Documentation
- #9212: @zackfern Delimit passed flags in dockerfile-start-cmd example with a double-dash
### Tests
- #9215: @josegonzalez chore(deps): drop unused packages from dockerfile-release lock
- #9214: @dependabot[bot] chore(deps): bump body-parser from 2.3.0 to 2.4.0 in /tests/apps/checks-root in the npm_and_yarn group across 1 directory
- #9206: @dependabot[bot] chore(deps): bump ruby from 4.0.7 to 4.0.9 in /tests/apps/dockerfile-entrypoint
- #9203: @dependabot[bot] chore(deps): bump sass from 1.106.0 to 1.106.1 in /tests/apps/multi
- #9197: @dependabot[bot] chore(deps): bump golang from 1.27.2 to 1.27.3 in /tests/apps/gogrpc
- #9193: @dependabot[bot] chore(deps): bump golang from 1.27.2 to 1.27.3 in /tests/apps/go-fail-postdeploy
- #9195: @dependabot[bot] chore(deps): bump golang from 1.27.2 to 1.27.3 in /tests/apps/go-fail-predeploy
- #9194: @dependabot[bot] chore(deps): bump golang from 1.27.2 to 1.27.3 in /tests/apps/zombies-dockerfile-no-tini
- #9192: @dependabot[bot] chore(deps): bump golang from 1.27.2 to 1.27.3 in /tests/apps/zombies-dockerfile-tini
- #9191: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 298 to 299 in /tests/apps/php
### Dependencies
- #9220: @dokku-bot chore: bump dokku-update to 0.11.0
- #9216: @josegonzalez chore(deps): upgrade go-jose to 4.1.6
- #9210: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize in /plugins/scheduler-k3s
- #9209: @dependabot[bot] chore(deps): bump mkdocs-material from 9.7.7 to 9.8.0 in /docs/_build
- #9208: @dependabot[bot] chore(deps): bump importlib-metadata from 9.0.1 to 10.1.0 in /docs/_build
- #9207: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.13.0 to 0.13.1 in /plugins/openresty-vhosts
- #9204: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager in /plugins/scheduler-k3s
- #9199: @dependabot[bot] chore(deps): bump traefik from v3.7.15 to v3.7.16 in /plugins/traefik-vhosts
- #9202: @dependabot[bot] chore(deps): bump soupsieve from 3.0.3 to 3.0.4 in /docs/_build
- #9201: @dependabot[bot] chore(deps): bump zipp from 4.1.1 to 4.1.2 in /docs/_build
- #9198: @dependabot[bot] chore(deps): bump python-dateutil from 2.9.0.post0 to 3.0.0 in /docs/_build
The bootstrap script now shows each install step in the style of Dokku's log output, with a live view of the running step when run in a terminal, per-step timings, a timestamped log file under `/var/log/dokku`, and a summary at the end. A failed step prints its full output. After installing, it adds root's SSH keys and sets the server's IP as the global domain when those are not already configured. `DOKKU_VERBOSE=1` streams all command output, and `NO_COLOR=1` disables colors.
Resolves a denial of service when parsing JWKs that contain crafted RSA keys. The package is an indirect dependency of the k3s scheduler via the ACME client used by traefik.
The `dockerfile-release` test app lock still pinned `setuptools` and `pytz` even though neither is in the `Pipfile` nor required by any locked package. Removing them clears the outstanding setuptools advisory without carrying a dependency the app never uses.
# History
## 0.38.32
Install/update via the bootstrap script:
```shell
wget -NP . https://dokku.com/install/v0.38.32/bootstrap.sh
sudo DOKKU_TAG=v0.38.32 bash bootstrap.sh
```
### Bug Fixes
- #9189: @josegonzalez Release deploy locks on exit and honor apps:lock
- #9128: @sean0407 Forward forced cleanup flag (#9127)
- #9182: @josegonzalez Read alternate image tags from the retired container
- #9180: @josegonzalez Clean up stale extracted files on deploy
- #9176: @byjg Haproxy letsencrypt integration and upgrade EasyHAProxy to 6.5.1
- #9175: @josegonzalez Only clear the requested resource type
- #9172: @josegonzalez Serialize concurrent property updates
- #9169: @josegonzalez Accept all docker memory units in resource values
- #9129: @josegonzalez Respect DOKKU_LIB_HOST_ROOT for storage bind mounts
- #9130: @josegonzalez Preserve arguments such as --version and -n when running commands over SSH
- #9148: @josegonzalez Do not retire an image that another app uses as its source image
- #9131: @josegonzalez Keep the git source-image property in sync and harden the deploy lock
- #9102: @josegonzalez Allow enabling nginx-vhosts when skipped at install
### New Features
- #9190: @josegonzalez Add a whole-set form and clear for chart overrides
- #9152: @Schlepptop Make nginx-proxy-http-version configurable per app or globally
- #9104: @josegonzalez Allow cron-entries to set a per-entry MAILTO
### Documentation
- #9132: @SatvikMishra08 Fix broken internal anchors and links
- #9119: @fmtappendf Fix some typos in comment
- #9116: @cevheri Add LibreDB Studio plugin to community plugin list
### Tests
- #9186: @dependabot[bot] chore(deps): bump sass from 1.105.2 to 1.106.0 in /tests/apps/multi
- #9187: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 297 to 298 in /tests/apps/php
- #9181: @josegonzalez Pause the retire timer during source image test
- #9170: @josegonzalez Fix pre-existing gofmt and markdown lint issues
- #9155: @dependabot[bot] chore(deps): bump golang from 1.27.1 to 1.27.2 in /tests/apps/zombies-dockerfile-no-tini
- #9163: @dependabot[bot] chore(deps): bump sass from 1.105.1 to 1.105.2 in /tests/apps/multi
- #9161: @dependabot[bot] chore(deps): bump golang from 1.27.1 to 1.27.2 in /tests/apps/gogrpc
- #9160: @dependabot[bot] chore(deps): bump golang from 1.27.1 to 1.27.2 in /tests/apps/go-fail-predeploy
- #9159: @dependabot[bot] chore(deps): bump golang from 1.27.1 to 1.27.2 in /tests/apps/zombies-dockerfile-tini
- #9158: @dependabot[bot] chore(deps): bump golang from 1.27.1 to 1.27.2 in /tests/apps/go-fail-postdeploy
- #9150: @dependabot[bot] chore(deps): bump python from 3.15.0rc3-bookworm to 3.15.0-bookworm in /tests/apps/dockerfile-release
- #9145: @dependabot[bot] chore(deps): bump djangorestframework from 3.18.1 to 3.18.3 in /tests/apps/dockerfile-release
- #9144: @dependabot[bot] chore(deps): bump express from 5.2.1 to 5.3.0 in /tests/apps/nodejs-express-noprocfile
- #9143: @dependabot[bot] chore(deps): bump express from 5.2.1 to 5.3.0 in /tests/apps/nodejs-express-noappjson
- #9141: @dependabot[bot] chore(deps): bump express from 5.2.1 to 5.3.0 in /tests/apps/nodejs-express
- #9140: @dependabot[bot] chore(deps): bump express from 5.2.1 to 5.3.0 in /tests/apps/gitsubmodules
- #9139: @dependabot[bot] chore(deps): bump python from 3.15.0rc2-bookworm to 3.15.0rc3-bookworm in /tests/apps/dockerfile-release
- #9138: @dependabot[bot] chore(deps): bump express from 5.2.1 to 5.3.0 in /tests/apps/dockerfile-procfile-bad
- #9135: @dependabot[bot] chore(deps): bump express from 5.2.1 to 5.3.0 in /tests/apps/checks-root
- #9134: @dependabot[bot] chore(deps): bump express from 5.2.1 to 5.3.0 in /tests/apps/dockerfile-procfile
- #9133: @dependabot[bot] chore(deps): bump express from 5.2.1 to 5.3.0 in /tests/apps/dockerfile-app-json-formations
- #9115: @dependabot[bot] chore(deps): bump django from 5.2.17 to 5.2.18 in /tests/apps/dockerfile-release
- #9113: @dependabot[bot] chore(deps): bump source-map-js from 1.2.1 to 1.2.2 in /tests/apps/multi in the npm_and_yarn group across 1 directory
- #9112: @dependabot[bot] chore(deps): bump proxy-addr from 2.0.7 to 2.0.8 in /tests/apps/checks-root in the npm_and_yarn group across 1 directory
- #9110: @dependabot[bot] chore(deps): bump sass from 1.105.0 to 1.105.1 in /tests/apps/multi
- #9109: @dependabot[bot] chore(deps): bump monolog/monolog from 3.12.0 to 3.12.1 in /tests/apps/php
- #9106: @dependabot[bot] chore(deps): bump werkzeug from 3.1.8 to 3.1.9 in /tests/apps/python-flask
- #9091: @dependabot[bot] chore(deps): bump sass from 1.104.1 to 1.105.0 in /tests/apps/multi
- #9086: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 295 to 297 in /tests/apps/php
### Dependencies
- #9184: @josegonzalez chore: group kubernetes dependency updates
- #9183: @dokku-bot chore: bump herokuish to 0.11.21
- #9177: @dependabot[bot] chore(deps): bump timberio/vector from 0.59.0-debian to 0.60.0-debian in /plugins/logs
- #9179: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 in /plugins/scheduler-k3s
- #9178: @dependabot[bot] chore(deps): update markdown requirement from <3.12,>=3.11.1 to >=3.11.2,<3.12 in /docs/_build
- #9173: @dokku-bot chore: bump herokuish to 0.11.20
- #9174: @dokku-bot chore: bump pack to 0.40.10
- #9164: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize in /plugins/scheduler-k3s
- #9166: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 in /plugins/scheduler-k3s
- #9157: @dependabot[bot] chore(deps): update markdown requirement from <3.12,>=3.11 to >=3.11.1,<3.12 in /docs/_build
- #9156: @dependabot[bot] chore(deps): bump pymdown-extensions from 12.2 to 12.3 in /docs/_build
- #9167: @dependabot[bot] chore(deps): bump python from 3.15.0rc3-alpine to 3.16.0a1-alpine in /docs/_build
- #9165: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 in /plugins/scheduler-k3s
- #9162: @dependabot[bot] chore(deps): bump traefik from v3.7.14 to v3.7.15 in /plugins/traefik-vhosts
- #9149: @dependabot[bot] chore(deps): bump soupsieve from 2.10 to 3.0.3 in /docs/_build
- #9146: @dependabot[bot] chore(deps): bump golang.org/x/crypto in /plugins/common
- #9142: @dependabot[bot] chore(deps): bump pymdown-extensions from 12.1 to 12.2 in /docs/_build
- #9137: @dependabot[bot] chore(deps): bump traefik from v3.7.13 to v3.7.14 in /plugins/traefik-vhosts
- #9136: @dependabot[bot] chore(deps): bump timberio/vector from 0.58.0-debian to 0.59.0-debian in /plugins/logs
- #9125: @dokku-bot chore: bump herokuish to 0.11.19
- #9123: @dependabot[bot] chore(deps): bump python from 3.15.0rc2-alpine to 3.15.0rc3-alpine in /docs/_build
- #9124: @dependabot[bot] chore(deps): bump golang.org/x/sync in /plugins/common
- #9121: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 in /plugins/scheduler-k3s
- #9118: @dependabot[bot] chore(deps): bump zipp from 4.1.0 to 4.1.1 in /docs/_build
- #9114: @dependabot[bot] chore(deps): bump markupsafe from 3.0.3 to 3.0.4 in /docs/_build
- #9111: @dependabot[bot] chore(deps): bump sigs.k8s.io/kustomize/api in /plugins/scheduler-k3s
- #9099: @dependabot[bot] chore(deps): bump k8s.io/kubernetes in /plugins/scheduler-k3s
- #9098: @dependabot[bot] chore(deps): bump k8s.io/kubectl in /plugins/scheduler-k3s
- #9094: @dependabot[bot] chore(deps): bump github.com/kedacore/keda/v2 in /plugins/scheduler-k3s
- #9090: @dependabot[bot] chore(deps): bump soupsieve from 2.9.2 to 2.10 in /docs/_build
- #9093: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer in /plugins/scheduler-k3s
- #9101: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.12.1 to 0.13.0 in /plugins/openresty-vhosts
- #9092: @dependabot[bot] chore(deps): bump github.com/onsi/gomega in /plugins/buildpacks
- #9100: @dokku-bot chore: bump herokuish to 0.11.18
- #9097: @dependabot[bot] chore(deps): bump k8s.io/client-go in /plugins/scheduler-k3s
- #9089: @dependabot[bot] chore(deps): bump pymdown-extensions from 12.0.1 to 12.1 in /docs/_build
- #9088: @dependabot[bot] chore(deps): update markdown requirement from <3.11,>=3.10.3 to >=3.11,<3.12 in /docs/_build
The `scheduler-k3s:charts:set` command only changed one chart override at a time, so matching a chart's stored overrides to a declared set meant reading the report, computing the difference in both directions and issuing one command per added, changed and removed property, with a failure partway through leaving a mixture of the two sets. A `--replace` flag on `scheduler-k3s:charts:set` now takes a chart name followed by `property=value` pairs and writes the declared map for that chart in a single call. Every pair is parsed before anything is written, so a rejected pair leaves the stored overrides untouched, and a pair ending in `=` stores an empty value, which the per-property form cannot express. An empty pair list is rejected rather than treated as a request to remove everything, and the new `scheduler-k3s:charts:clear` command handles that case by removing every override for a chart.
A deploy that exited before releasing its lock, such as after a failed build, left `.deploy.lock` behind, so `apps:locked` reported a lock nothing held and the build record stayed running. The lock is now released and the record finalized whenever the deploy exits. `apps:lock` never actually blocked deploys, as it only created the file deploys lock. It now creates its own lock that git pushes, `ps:rebuild`, `git:sync`, and the `git:from-*` commands refuse to deploy past, and `apps:locked` only reports a lock when the app was locked via `apps:lock` or a deploy is in progress. Locks created by `apps:lock` in older versions are migrated on install.
On hosts using the containerd image store, an app's previous image can be removed once its tag moves to a new build, even while the old container is still running. The alternate tag lookup then failed, so an image deployed via `git:from-image` was never queued for retirement.
The periodic `dokku-retire` timer could run `ps:retire` between the test's final deploy and its own `ps:retire` call, processing the retire list first and leaving the test with no output to assert on.
A failed deploy could leave a non-empty extraction directory behind, such as the scheduler-k3s `kustomization` directory, which caused every following deploy to fail. Leftovers from previous deploys are now fully removed, removing a directory such as `config/kustomize` from the repository now removes the stored copy, and openresty includes now persist across restarts.
The letsencrypt integration set EASYHAPROXY_LETSENCRYPT_EMAIL (with a stray
quote) and a letsencrypt label, which EasyHAProxy has not read since before
4.6.0. With an email set, http redirected to https and nothing answered on 443.
- Pass EASYHAPROXY_CERTBOT_EMAIL and EASYHAPROXY_CERTBOT_SERVER.
- Label the app with certbot=true on its http definition: EasyHAProxy requests
the certificate and serves https itself, so the separate 443 definition goes.
https labels are only added when a letsencrypt email is set, as documented.
- Keep the certificates in /var/lib/dokku/data/haproxy.
- Turn the stats page off with HAPROXY_STATS_ENABLE=false.
- Upgrade EasyHAProxy from 6.1.1 to 6.5.1.
Running `resource:limit-clear` or `resource:reserve-clear` without `--process-type` deleted every resource property for the app, so clearing limits also removed reservations and vice versa. Both commands now clear only their own resource type across all process types.
Concurrent commands for the same app could each read a setting, change it, and write it back, so all of them exited successfully while silently dropping changes made by the others. Updates to app settings are now serialized so every change is kept, and readers never see a partially written value.
Memory values with uppercase or other docker-supported units such as `1G`, `512M` or `1GiB` are now passed to docker unchanged instead of having an extra `m` appended, and the k3s scheduler interprets memory values with the same binary units so a value means the same thing on both schedulers. Invalid `--memory` and `--memory-swap` values are now rejected by `resource:limit` and `resource:reserve`. Closes#9153.
/dev/null is a character device but not a terminal. Treating it as a TTY
made docker run -it fail under docker exec without -t, which broke the
Dokku-in-Docker storage bats job in CI.
Add a Dokku-in-Docker bats suite that writes under DOKKU_LIB_HOST_ROOT and
checks storage:exec, set those env vars in the docker CI container, and add
unit bats for the bare-metal (unset) and rewritten docker-args paths.
When Dokku runs in Docker with a shared socket, create/chown still use
DOKKU_LIB_ROOT inside the container, but Docker -v flags for storage:exec
and deploy must use DOKKU_LIB_HOST_ROOT so the daemon mounts the host data
volume instead of the real host's /var/lib/dokku.
Closes#9126.
deploy_app cannot push onto an app that already has git history from git:from-image. git:from-archive deploys and clears source-image the same way git_4 already does.
Image retirement removes images by id, so a queued image could be deleted even though another app had since started using it as its git:from-image source image. Skip such images at retire time and drop them from the retire list.
The git_4 test images are also given a per-app label so they no longer share a cached image id, which let a retire entry left by one test delete the next test's source image.
xargs without a command runs /bin/echo per word, which interprets --version, --help, -n, -e and -E as its own options. Use printf with a NUL delimiter instead.
Closes#9120
git-from-directory updated the deploy branch before taking the deploy
lock and rolled it back after releasing it. The lock is now held for the
whole update, build, and rollback, and the rollback only moves the branch
if it still points at the commit that was created.
A failed lock attempt and apps:lock both truncated the lock file,
dropping the build id of the in-progress deploy that builds:cancel and
builds:output read. Releasing the lock unlocked before removing the
file, so a waiter could end up holding a lock on a deleted file while
another deploy locked a new one. The file is now removed before
unlocking, and acquiring retries when the locked file is no longer the
one at the lock path.
The source-image property was written before the deploy lock was taken
and was not restored when a deploy failed, so after a rejected
git:from-image or git:load-image it named an image that was not
deployed, and later builds extracted app.json, Procfile, and CHECKS
from it. Deploys from other sources also built while the old image was
still set.
Entry points now pass the desired value via DOKKU_GIT_SOURCE_IMAGE and
git_receive_app applies it while holding the deploy lock, restoring the
previous value on failure. git:sync clears the property under the lock
when it fetches new code.
Closes#9117
A task with its own `MAILTO` is now written in place, followed by a line resetting `MAILTO` to the global value, or to the `dokku` user when none is set, rather than grouped at the end of the crontab, so no other task is mailed to its recipient whatever order the tasks are written in.
The `cron-entries` trigger is now passed `json` as a second argument, and plugins may print each task as a json object on its own line. A task that sets a `mailto` is written to the crontab under a `MAILTO` of its own, and its output is mailed to that address as well as appended to its log file, so plugins such as the datastore plugins can have the output of a specific scheduled task mailed to a specific person.
Installing with the `dokku/nginx_enable` debconf option set to `false` left the nginx-vhosts plugin disabled with no way to turn it back on, as `dokku plugin:enable nginx-vhosts` reported that the plugin does not exist. The plugin can now be enabled after installation, and the debconf documentation explains the option's effect and how to reverse it.
The `--scheduler-k3s-global-node-sysctls-image` and `--scheduler-k3s-global-node-sysctls-pause-image` report flags returned the built-in default when nothing was stored, so an unset image could not be told apart from one set to the default. They now report the stored value like every other global flag, and new `--scheduler-k3s-computed-node-sysctls-image` and `--scheduler-k3s-computed-node-sysctls-pause-image` flags report the image that is actually used.
Procfile commands for the `docker-local` scheduler were split differently on deploy and on `dokku run`. Deploys failed on escaped quotes, and `dokku run` ignored quotes entirely. Both now split commands using shell quoting rules, and commands containing bare shell operators fail with a clear error, matching the `k3s` scheduler.
# History
## 0.38.30
Install/update via the bootstrap script:
```shell
wget -NP . https://dokku.com/install/v0.38.30/bootstrap.sh
sudo DOKKU_TAG=v0.38.30 bash bootstrap.sh
```
### New Features
- #9074: @josegonzalez Allow storage:mount --replace to vary fields per mount
- #9075: @josegonzalez Report each node-sysctls scope's stored map
### Tests
- #9071: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.83.2 to 1.84.0 in /tests/apps/gogrpc
`scheduler-k3s:node-sysctls:report` renders each scope's resolved set, so a profile's entry carries every globally-set sysctl underneath its own and there is no way to read back the map the profile itself stores. Since `node-sysctls:set --replace --profile` and `node-sysctls:clear --profile` write only that map, a tool converging a profile against a declared map sees permanent drift. The new `--stored` flag reports the map each scope owns, and `--global` and `--profile` narrow the report to a single scope. The default output is unchanged.
`storage:mount --replace` writes one process type's whole attachment set in a single call, but only part of an attachment could vary between the mounts in that call: each mount carried its own `ro` and Docker mount options while `--phase`, `--volume-subpath` and `--volume-chown` were fixed for the whole call, so an app whose mounts differ only in subpath still needed one call per mount, which is the non-atomic convergence `--replace` exists to remove. Each mount's option list now also carries `phase=`, `volume-subpath=` and `volume-chown=` tokens alongside `ro`, its new opposite `rw` and the Docker options it already passed through, and so that each attribute has exactly one spelling the five matching flags are now rejected with `--replace` - one of which, `--volume-options`, was previously accepted and silently dropped. `--process-type` still scopes the whole call, since it selects which mounts the call is authoritative over rather than describing any one of them. Every mount is parsed and validated before anything is written, so a rejected token leaves the stored mounts untouched. The documentation added for `--replace` was also marked as new in 0.38.28 in four places, but it shipped in 0.38.29.
# History
## 0.38.29
Install/update via the bootstrap script:
```shell
wget -NP . https://dokku.com/install/v0.38.29/bootstrap.sh
sudo DOKKU_TAG=v0.38.29 bash bootstrap.sh
```
### Bug Fixes
- #9060: @josegonzalez Preserve global flags when sudo is sudo-rs
- #9055: @josegonzalez Validate nginx templates as the running nginx user
- #9054: @josegonzalez Honor the pinned chart version when upgrading
- #9049: @josegonzalez Report config write failures instead of exiting 0
- #9050: @josegonzalez Reject negative process counts when scaling
### New Features
- #9058: @josegonzalez Add a whole-set form for an app's storage mounts
- #9057: @josegonzalez Add a way to read back registry login state
- #9051: @josegonzalez Add a whole-set form for scheduler-k3s metadata
### Documentation
- #9070: @s01ipsist Document Ubuntu 26.04 as a supported installation target
### Tests
- #9062: @josegonzalez Pass force to the datastore command that owns it
- #9061: @josegonzalez Call the datastore create command the way it is documented
### Dependencies
- #9064: @dependabot[bot] chore: group gomod dependabot updates across all plugins
- #9066: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.43.0 to 1.43.1 in /plugins/buildpacks
- #9067: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.40.0 to 1.41.0 in /plugins/scheduler-k3s
- #9063: @dependabot[bot] chore(deps): bump pymdown-extensions from 12.0 to 12.0.1 in /docs/_build
- #9068: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.43.0 to 1.43.1 in /plugins/scheduler-k3s
- #9052: @dependabot[bot] chore(deps): bump pymdown-extensions from 11.0.2 to 12.0 in /docs/_build
### Other
- #9069: @josegonzalez Honor storage attachment process types at deploy time
- #9011: @visheshgubrani Propagate command exit code on dokku run
Ubuntu 26.04 was added to the bootstrap installer's supported versions
in #8791 (released in 0.38.28). Update the README, installation
requirements and testing docs to match.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A pin added in March 2024 held godotenv at `v1.2.0` to keep a parser change from reaching users, but it lives in the scheduler-k3s manifest while plugins build with the workspace disabled, so it never applied to the config plugin it was meant to protect and that plugin moved to `v1.5.1` in April 2026. All it did was leave the scheduler-k3s binary reading ENV files differently from every other plugin, and hide the difference from the test suite, which runs through the workspace where the pin does apply.
The ps help text was the only one of the nineteen plugin help constants to end in a newline, which made `go vet` reject the `fmt.Println` that prints it. The help dispatcher strips the blank line before it reaches anyone, so the rendered output is unchanged.
An ENV file holding an entry the parser rejects made every config read for that app fail outright, so the existing recovery that drops invalid keys and rewrites the file never had a chance to run. This went unnoticed because two environment test fixtures had gone stale against parser leniency that no longer exists, and the module manifest was out of date enough that its tests refused to run at all.
Plugin modules had drifted apart over time, leaving `github.com/onsi/gomega` pinned at `v1.43.0` in two plugins and `v1.43.1` in three others, with similar splits across a number of indirect dependencies. The `config` and `storage` plugins also carried manifests that were out of date enough that their Go tests refused to run, and the `builds` plugin was missing from the workspace entirely.
Dependabot opened a separate pull request per plugin directory, so bumping a dependency shared across plugins produced up to twenty pull requests that could be reviewed and merged out of step with each other. Updates are now grouped by dependency name across every plugin module, which also brings in the seventeen modules that previously had no coverage at all.
The ephemeral containers that run `app.json` deploy tasks ignored a failure from the `docker-args-deploy` and `docker-args-process-deploy` triggers, so a plugin refusing to describe the container produced a task that ran without the arguments it had asked for, while the very same failure aborted the deploy moments later on the scheduler's own path. Both are now fatal, which the trigger documentation states. Storage no longer reports a scheduler mismatch for an app deploying with another scheduler, since those triggers are fired for every app whatever its scheduler and a k3s app's volumes are mounted by k3s rather than bound into a container here.
Mounting one storage entry at two container paths named the same pod volume twice, which Kubernetes rejects as a duplicate, so the chart never applied even though the k3s documentation offers mounting a claim "into the same app at multiple subpaths" as a supported shape. A pod now declares each entry once and binds it at each path it was mounted at. Rendering the chart in a test also surfaced that a cron schedule was emitted unquoted, so any schedule beginning with `*` produced a manifest that could not be parsed at all.
An attachment's process type was recorded, reported and used to scope `storage:mount --replace` and `storage:unmount --all`, but nothing read it when the volume was actually mounted, so `storage:mount <app> <entry> --container-dir <path> --process-type web` bound the volume into every process rather than only `web`. Both schedulers now scope a mount to the process type it was declared for, and containers that belong to no process type - a `dokku run` one-off, an `app.json` deploy task, a k3s cron job - receive default-scoped mounts only. A container path can now be claimed by a single mount per process type, and `storage:migrate` drains process-scoped `-v` lines out of `docker-options` rather than only the default-scoped ones.
The test used `DOKKU_IMAGE` as a variable that does not cross the privilege drop, which stopped being true once every environment-overridable variable was forwarded. It now reads the `--preserve-env` argument itself, so it tests the contract rather than one member of the list.
`DOKKU_INIT_SYSTEM` reaches the `dokku` user only through the environment while the Docker image is being built, so narrowing the forwarded set left `nginx:reload` taking the systemd branch during `debian/postinst` and blocking on a sudo password prompt until the build timed out. The forwarded set now covers every variable `dokku` honours from the environment, and `DOKKU_PRESERVE_ENV` still carries anything beyond it.
`sudo-rs`, the default sudo on Ubuntu 25.10 and later, does not support preserving the entire environment, so `--quiet`, `--trace`, `--force` and `--app` stopped taking effect once `dokku` re-executed itself as the `dokku` user, and a warning was printed on every invocation. Variables outside the set dokku forwards no longer cross that boundary and can be named in `DOKKU_PRESERVE_ENV` instead.
The datastore destroys were written with dokku's global `--force`, which works only because dokku translates it into `DOKKU_APPS_FORCE_DELETE` and the plugin reads that environment variable back. The flag belongs to the subcommand, which declares it, so it is passed there instead.
Only the datastore calls move. `network:destroy` and `storage:destroy` are dokku's own commands and keep dokku's own convention.
The ports and app-json tests passed the app name to `postgres:create` and `redis:create` a second time, as a positional the command has never taken. The bash datastore plugins parsed their arguments with getopts and simply stopped at it; those plugins now dispatch to a binary that reports the count instead, so every dokku pull request fails on `This command requires 2 arguments, 3 arguments given`.
The documented form takes the service alone, as `docs/deployment/application-deployment.md` shows. The `:link` and `:unlink` calls beside these are unchanged, since those do take two.
The `storage` plugin mounted and unmounted one attachment at a time, so matching an app's mounts to a declared set meant reading `storage:list`, computing the difference in both directions and issuing one `storage:mount` or `storage:unmount` per addition and removal, with a failure partway through leaving the app holding a mixture of the two sets.
A `--replace` flag on `storage:mount` now writes the declared set in a single call, taking `name:container-dir` pairs whose optional third field carries the same mount options the legacy colon form accepts. Every pair is parsed and validated before anything is written, so a rejected pair leaves the stored mounts untouched. The remaining mount-time flags scope the whole call the way `--process-type` already scoped a single mount, which means an omitted `--process-type` replaces the `_default_` scope and leaves mounts scoped to a named process type in place.
An empty pair list is rejected rather than treated as a request to unmount everything, so a generated list that expands to nothing cannot silently drop an app's storage. `storage:unmount --all` handles that case instead, taking an optional `--process-type` filter and treating a scope that matches nothing as a no-op so it stays idempotent.
`storage:unmount` also accepts more than one mount at a time, naming each by entry name or by the colon form, and resolves every argument against the app's attachments before removing any of them.
The help listing now carries one entry per subcommand with its flags inline rather than a row per variant, and the command listing at the top of the storage documentation matches it line for line. Folding those rows together also surfaced `--global` on `storage:report` and `storage:migrate`, neither of which both listings had mentioned.
Finally, `--volume-chown` is now validated on mount, where a typo previously exited zero and wrote a value that only the report ever read.
There was no way to ask whether Dokku was already logged in to a registry with a given credential, so tooling converging a declared state onto a server had to re-run `registry:login` on every pass purely to discover that nothing needed to change. The new `registry:auth-status` command compares a supplied credential against the stored one and reports the result through its exit code alone, distinguishing a server with no credential from one whose credential differs, and from one whose secret a docker credential helper or an identity token puts out of reach. An `auth-servers` triad on `registry:report` exposes which servers have credentials without emitting any part of one.
Docker stores Docker Hub under the index server address rather than the hostname the user logged in with, and `docker logout` only erases the exact key it is given, so `registry:logout docker.io` reported success while leaving the credential in place. The server is now resolved to the key docker actually stores before the logout runs.
The wrapper config used to run `nginx -t` against an app's rendered `nginx.conf` declared no `user` directive, so nginx fell back to its compile-time default and chowned every `proxy_cache_path` directory to that user, including directories that already existed. On Debian and Ubuntu that default is `nobody`, so deploying an app shipping a custom `nginx.conf.sigil` took the running server's live cache directory away for the length of the build and turned every request the cache would serve into an HTTP 500, healing only once the deploy's own reload restored the ownership. The wrapper now declares the same `user` as the host's main nginx config, which is also exposed to custom `validate-config` templates as `$.NGINX_USER`.
Helm resolves its cache, config and data homes from `$HOME`, but dokku runs `scheduler-k3s:initialize` as root and every other subcommand as the dokku user, so those paths are not always readable by the user a command ends up as. Helm treats a repositories file it cannot read as a fatal error rather than an absent one, which made chart resolution fail outright even though dokku never reads an entry from that file. Each call now points helm at a private scratch directory that is removed once the chart has been located.
`scheduler-k3s:ensure-charts` announced the pinned chart version but installed whatever the chart repository had published most recently, and an affected cluster could not be brought back in line because a version mismatch was reported and then skipped. Charts now install at the version dokku pins, and a release that has drifted is reconciled back to it. Dokku also no longer registers helm repositories it never read, and the previously undocumented `ensure-charts` and `initialize` flags now appear in the help output.
The `scheduler-k3s` metadata commands were all per-key, so matching an app's stored annotations, labels or node sysctls to a declared set meant reading the report, computing the difference in both directions and issuing one command per added, changed and removed key, with a failure partway through leaving a mixture of the two sets. A `--replace` flag on `annotations:set`, `labels:set`, `node-sysctls:set` and `autoscaling-auth:set` now writes the declared map in a single call, taking trailing `key=value` pairs everywhere except `autoscaling-auth:set`, which keeps its repeatable `--metadata` flag. Every pair is parsed before anything is written, so a rejected pair leaves the stored map untouched, and a pair ending in `=` stores an empty value, which the per-key form cannot express. An empty pair list is rejected rather than treated as a request to remove everything, so a generated list that expands to nothing cannot silently drop an app's metadata; `annotations:clear`, `labels:clear` and `node-sysctls:clear` handle that case, taking the same optional filters as their report commands. The `--resource-type` flag and the app name are now validated on the set commands as well, where a typo previously exited zero and wrote a property that no report and no deploy ever read.
A process count below zero was accepted and stored verbatim, leaving the scheduler to reject the value long after the command reported success. Counts below zero are now rejected, as are process tuples that omit a process type, across `ps:scale`, the `ps-set-scale` trigger and the `formation` key of `app.json`. A negative count persisted by an earlier version stays readable so it can be corrected with `ps:scale`.
The config tests wrote to the real `DOKKU_ROOT` and `DOKKU_LIB_ROOT`, including the global `ENV` file, and relied on a `dokku` user and group existing. That held only because the result of applying ownership was discarded; now that it is reported, the tests failed under `ci-go-coverage`, which runs them in a build image that has no `dokku` user. They now use the temporary directories and current-user ownership the migration tests already use.
`config:set`, `config:unset` and `config:clear` discarded the result of writing the app's environment, so a write that never landed still printed `Setting config vars`, still fired the `post-config-update` trigger, still restarted the app and still exited 0. The `config-set` and `config-unset` plugin triggers behaved the same way, which meant an `app.json` `env` block that could not be persisted deployed anyway with the variables missing. The environment is now staged in a temporary file and renamed into place, so a write that fails partway leaves the previous environment intact, and that file is created at `0600` rather than being created at `0644` and chmodded afterwards. `apps:clone` and `apps:rename` never applied that chmod at all and left the new app's environment world-readable.
# History
## 0.38.28
Install/update via the bootstrap script:
```shell
wget -NP . https://dokku.com/install/v0.38.28/bootstrap.sh
sudo DOKKU_TAG=v0.38.28 bash bootstrap.sh
```
### Bug Fixes
- #9033: @mvysny Discard the build record git:sync starts when nothing is built
- #9035: @vulragrag-star Fix builds:output exiting 0 for unknown build ids
- #9010: @visheshgubrani Reject scheduler mismatch at mount time
- #8975: @BetterAndBetterII Treat is_app_listening as a boolean, not a non-empty string
- #8972: @josegonzalez Validate k3s profile names against derived release name
- #8937: @josegonzalez Retire cron containers past their active deadline
### New Features
- #9047: @josegonzalez Release dokku on resolute
- #8791: @akhil-naidu Add Ubuntu 26.04 LTS to bootstrap supported versions
- #9003: @josegonzalez Distinct git:auth-status exit codes for netrc state
- #9001: @josegonzalez Add --clear and --replace flags to ps:scale
- #8996: @josegonzalez Add fingerprint and serial to certs:report
### Documentation
- #9036: @meteozond Add dokku-generic to community plugins
### Tests
- #9046: @dependabot[bot] chore(deps): bump ruby from 4.0.6 to 4.0.7 in /tests/apps/dockerfile-entrypoint
- #9045: @dependabot[bot] chore(deps): bump sass from 1.104.0 to 1.104.1 in /tests/apps/multi
- #9032: @dependabot[bot] chore(deps): bump djangorestframework from 3.18.0 to 3.18.1 in /tests/apps/dockerfile-release
- #9038: @dependabot[bot] chore(deps): bump monolog/monolog from 3.11.0 to 3.12.0 in /tests/apps/php
- #9040: @dependabot[bot] chore(deps): bump psycopg2-binary from 2.9.12 to 2.9.13 in /tests/apps/dockerfile-release
- #9014: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 294 to 295 in /tests/apps/php
- #9016: @dependabot[bot] chore(deps): bump slim/slim from 4.15.2 to 4.15.3 in /tests/apps/php
- #9019: @dependabot[bot] chore(deps): bump sass from 1.103.1 to 1.104.0 in /tests/apps/multi
- #9015: @dependabot[bot] chore(deps): bump monolog/monolog from 3.10.0 to 3.11.0 in /tests/apps/php
- #9004: @dependabot[bot] chore(deps): bump qs from 6.15.2 to 6.16.0 in /tests/apps/checks-root
- #9005: @dependabot[bot] chore(deps): bump golang from 1.27.0 to 1.27.1 in /tests/apps/gogrpc
- #9006: @dependabot[bot] chore(deps): bump golang from 1.27.0 to 1.27.1 in /tests/apps/zombies-dockerfile-no-tini
- #9007: @dependabot[bot] chore(deps): bump golang from 1.27.0 to 1.27.1 in /tests/apps/zombies-dockerfile-tini
- #9008: @dependabot[bot] chore(deps): bump golang from 1.27.0 to 1.27.1 in /tests/apps/go-fail-predeploy
- #9009: @dependabot[bot] chore(deps): bump golang from 1.27.0 to 1.27.1 in /tests/apps/go-fail-postdeploy
- #8998: @dependabot[bot] chore(deps): bump python from 3.15.0rc1-bookworm to 3.15.0rc2-bookworm in /tests/apps/dockerfile-release
- #8976: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.83.1 to 1.83.2 in /tests/apps/gogrpc
- #8970: @dependabot[bot] chore(deps): bump gunicorn from 26.1.0 to 26.2.0 in /tests/apps/dockerfile-release
- #8969: @dependabot[bot] chore(deps): bump gunicorn from 26.1.0 to 26.2.0 in /tests/apps/python-flask
- #8968: @dependabot[bot] chore(deps): bump gunicorn from 26.1.0 to 26.2.0 in /tests/apps/multi
- #8965: @dependabot[bot] chore(deps): bump sass from 1.102.0 to 1.103.1 in /tests/apps/multi
- #8962: @dependabot[bot] chore(deps): bump golang from 1.26.6 to 1.27.0 in /tests/apps/go-fail-predeploy
- #8961: @dependabot[bot] chore(deps): bump golang from 1.26.6 to 1.27.0 in /tests/apps/go-fail-postdeploy
- #8960: @dependabot[bot] chore(deps): bump golang from 1.26.6 to 1.27.0 in /tests/apps/gogrpc
- #8959: @dependabot[bot] chore(deps): bump golang from 1.26.6 to 1.27.0 in /tests/apps/zombies-dockerfile-tini
- #8958: @dependabot[bot] chore(deps): bump golang from 1.26.6 to 1.27.0 in /tests/apps/zombies-dockerfile-no-tini
- #8957: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.83.0 to 1.83.1 in /tests/apps/gogrpc
- #8954: @dependabot[bot] chore(deps): bump gunicorn from 26.0.0 to 26.1.0 in /tests/apps/dockerfile-release
- #8952: @dependabot[bot] chore(deps): bump gunicorn from 26.0.0 to 26.1.0 in /tests/apps/multi
- #8951: @dependabot[bot] chore(deps): bump gunicorn from 26.0.0 to 26.1.0 in /tests/apps/python-flask
- #8943: @dependabot[bot] chore(deps): bump golang from 1.26.5 to 1.26.6 in /tests/apps/zombies-dockerfile-no-tini
- #8944: @dependabot[bot] chore(deps): bump golang from 1.26.5 to 1.26.6 in /tests/apps/go-fail-postdeploy
- #8942: @dependabot[bot] chore(deps): bump golang from 1.26.5 to 1.26.6 in /tests/apps/go-fail-predeploy
- #8941: @dependabot[bot] chore(deps): bump golang from 1.26.5 to 1.26.6 in /tests/apps/gogrpc
- #8940: @dependabot[bot] chore(deps): bump golang from 1.26.5 to 1.26.6 in /tests/apps/zombies-dockerfile-tini
- #8947: @dependabot[bot] chore(deps): bump sqlparse from 0.5.5 to 0.6.0 in /tests/apps/dockerfile-release
- #8939: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.11 to 1.36.12 in /tests/apps/gogrpc
### Dependencies
- #9027: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.14.0 to 3.14.1 in /plugins/docker-options
- #9039: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.56.0 to 0.57.0 in /plugins/common
- #9041: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.21.4 to 3.22.0 in /plugins/scheduler-k3s
- #9042: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.21.1 to 1.21.2 in /plugins/scheduler-k3s
- #9028: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.14.0 to 3.14.1 in /plugins/cron
- #9023: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.22.0 to 0.23.0 in /plugins/common
- #9017: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.55.0 to 0.56.0 in /plugins/common
- #9029: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.14.0 to 3.14.1 in /plugins/scheduler-k3s
- #9026: @dokku-bot chore: bump herokuish to 0.11.17
- #9024: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 1.0.0 to 1.0.1 in /plugins/scheduler-k3s
- #9025: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.22.0 to 0.23.0 in /plugins/scheduler-k3s
- #8978: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.42.1 to 1.43.0 in /plugins/common
- #8987: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.13.1 to 3.14.0 in /plugins/cron
- #9018: @dependabot[bot] chore(deps): bump traefik from v3.7.12 to v3.7.13 in /plugins/traefik-vhosts
- #9020: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.56 to 2.11.57 in /plugins/scheduler-k3s
- #9021: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.42.1 to 1.43.0 in /plugins/scheduler-k3s
- #8988: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.13.1 to 3.14.0 in /plugins/docker-options
- #9013: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.39.0 to 1.40.0 in /plugins/scheduler-k3s
- #9012: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.55 to 2.11.56 in /plugins/scheduler-k3s
- #8999: @dependabot[bot] chore(deps): bump k8s.io/kubectl from 0.36.4 to 0.37.0 in /plugins/scheduler-k3s
- #8997: @dependabot[bot] chore(deps): bump python from 3.15.0rc1-alpine to 3.15.0rc2-alpine in /docs/_build
- #8977: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.42.1 to 1.43.0 in /plugins/buildpacks
- #8981: @dependabot[bot] chore(deps): bump timberio/vector from 0.57.0-debian to 0.58.0-debian in /plugins/logs
- #8982: @dependabot[bot] chore(deps): bump traefik from v3.7.11 to v3.7.12 in /plugins/traefik-vhosts
- #8980: @dependabot[bot] chore(deps): bump click from 8.4.2 to 8.5.0 in /docs/_build
- #8983: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.13.0 to 0.13.1 in /plugins/ps
- #8984: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.36.4 to 1.37.0 in /plugins/scheduler-k3s
- #8985: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.13.0 to 0.13.1 in /plugins/scheduler-k3s
- #8986: @dependabot[bot] chore(deps): bump importlib-metadata from 9.0.0 to 9.0.1 in /docs/_build
- #8967: @dependabot[bot] chore(deps): bump pymdown-extensions from 11.0.1 to 11.0.2 in /docs/_build
- #8966: @dependabot[bot] chore(deps): bump k8s.io/kubectl from 0.36.3 to 0.36.4 in /plugins/scheduler-k3s
- #8964: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.36.3 to 1.36.4 in /plugins/scheduler-k3s
- #8963: @dependabot[bot] chore(deps): bump traefik from v3.7.10 to v3.7.11 in /plugins/traefik-vhosts
- #8953: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.54 to 2.11.55 in /plugins/scheduler-k3s
- #8950: @dependabot[bot] chore(deps): bump pygments from 2.20.0 to 2.21.0 in /docs/_build
- #8945: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.54.0 to 0.55.0 in /plugins/common
- #8949: @dokku-bot chore: bump herokuish to 0.11.16
- #8946: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.21.3 to 3.21.4 in /plugins/scheduler-k3s
When the per-build log is missing, builds:output fell through to
journalctl without checking that the build record exists. A typo'd or
never-recorded id then printed nothing and exited 0.
Validate the build record first and return "no such build" when it is
absent. Existing records with pruned logs still use the journalctl
fallback.
Fixes#9031
cmd-git-sync calls dokku_setup_build_capture before the clone/fetch,
because it cannot know whether there is anything to deploy until it has
fetched. Both paths that then decline to build - a plain `git:sync` with
no build flag, and a `--build-if-changes` poll that finds the remote
unchanged - returned without ever finalizing that record.
The record stayed at status=running forever, which has three
consequences on an app polled on a timer:
- PruneAppBuilds only runs from builds-record-finalize, so nothing
prunes the ticks while the app is idle; they accumulate without bound,
invisibly, because builds:list caps output at the retention count.
- The next real deploy's ReapAbandonedBuilds finalizes each of them as
status=failed, exit_code=-1, with finished_at stamped at the moment of
reaping - indistinguishable on disk from a build that really failed,
and with a duration that grew to the polling interval.
- That same deploy's prune then keeps the newest records by started_at,
which after an idle stretch are the ticks, deleting the last real
deploy's record and log.
Add a builds-record-discard trigger that removes a non-terminal record
and its log, and call it from both no-op paths in cmd-git-sync. The
trigger leaves missing and already-terminal records alone, so it can
never delete real history.
Fixes#9030
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `git:auth-status` command returned `1` both when a host had no `netrc` entry and when it had one that differed, so tooling wanting to tell a credential that would be created apart from one that would be replaced had to issue a second call with no username purely to find out which of the two it was. A host with no entry now exits `1`, a host whose entry differs from what was passed exits `2`, and invalid arguments exit `3`, leaving `0` to mean the stored state already matches. Called without a username the requested state is that no entry exists, so an existing entry is reported as `2` and `1` is never returned. Callers that only test zero versus non-zero are unaffected.
The `ps:scale` command merges the specified process types into the existing formation, so replacing an entire formation from the CLI required reading the current scale, diffing it, and issuing a second call to zero out each unlisted process type. The `--replace` flag treats the specified process types as the whole formation and sets the process count for every unspecified process type to zero, while the `--clear` flag resets the formation to the scale a newly created app has. The two flags cannot be combined, `--replace` requires at least one process type, and `--clear` accepts none. Process types cleared this way are now scaled down as part of the same command rather than being left running with a stale formation.
The target ran `go get github.com/onsi/gomega` inside the plugin module before every test run, which rewrote that plugin's `go.mod` and `go.sum` as a side effect of running tests. For a plugin that does not use gomega it added the module plus five transitive dependencies as unused indirect requirements, and for one that pins it the `go get` silently upgraded the pinned version, leaving the working tree dirty and fighting the dependency updates. The plugins whose tests use gomega already require it directly, and the test run passes `-mod=readonly`, so nothing needs to be fetched ahead of time.
The report described an installed certificate without carrying anything that identified it, so the only exact way to tell whether an app was serving a given certificate was to pull the full PEM back with `certs:show` and compare bytes. That moves certificate material off the server on every check, and the same command with `key` returns the private key. The new `--ssl-fingerprint` and `--ssl-serial` flags answer the same question in a single report read that discloses nothing sensitive. The sample output in the ssl documentation has also been corrected to match what the command actually prints.
The docker-args-process-deploy trigger initialized is_app_listening to
the string "false" and then tested it with [[ -n ... ]], which is always
true. Apps with domains, tls-internal, and no http/https port mapping
got a stray caddy.tls=internal label.
Fixes#8973
`scheduler-k3s:profiles:add` accepted profile names it could not later derive a valid helm release name from, so a profile was created successfully and only failed on commands with no obvious relationship to it: `profiles:remove` could not delete the profile it created, and a single bad name broke every subsequent node sysctls change, including the global scope and unrelated profiles. Names are now refused at add time against the release name they derive rather than against the profile name alone, and the paths that have to cope with a profile stored before this check keep working instead of erroring.
* master: (546 commits)
chore(deps): bump gunicorn in /tests/apps/dockerfile-release
chore(deps): bump github.com/traefik/traefik/v2
chore(deps): bump gunicorn from 26.0.0 to 26.1.0 in /tests/apps/multi
chore(deps): bump gunicorn in /tests/apps/python-flask
chore(deps): bump pygments from 2.20.0 to 2.21.0 in /docs/_build
chore: bump herokuish to 0.11.16
chore: bump go modules
chore(deps): bump golang.org/x/crypto in /plugins/common
chore(deps): bump sqlparse in /tests/apps/dockerfile-release
chore(deps): bump helm.sh/helm/v3 in /plugins/scheduler-k3s
chore(deps): bump golang in /tests/apps/zombies-dockerfile-no-tini
chore(deps): bump golang in /tests/apps/go-fail-postdeploy
chore(deps): bump golang in /tests/apps/go-fail-predeploy
chore(deps): bump golang from 1.26.5 to 1.26.6 in /tests/apps/gogrpc
chore(deps): bump golang in /tests/apps/zombies-dockerfile-tini
chore(deps): bump google.golang.org/protobuf in /tests/apps/gogrpc
fix: retire cron containers past their active deadline
Release 0.38.27
fix: report traefik dns-provider env vars as global keys
fix: do not require a local image for k3s deploys
...
# Conflicts:
# common.mk
# contrib/dependencies.json
Cron containers were never reaped once they exceeded their active deadline, so a hung cron task ran indefinitely instead of being retired after 24 hours as documented. `cron:run` now also accepts a `--ttl-seconds` argument, matching the one `dokku run` already takes.
The bootstrap installer rejected Ubuntu 26.04 LTS before any package
work happened because the version whitelist only knew 22.04/24.04.
Add 26.04 to the whitelist and supported-versions message so a fresh
install passes the gate; the existing codename fallback pins unknown
Ubuntu codenames to the noble packagecloud repo, which installs
cleanly on 26.04.
Closes#8768
Co-authored-by: Cursor <cursoragent@cursor.com>
* master: (35 commits)
Release 0.37.9
chore(deps): bump github.com/go-acme/lego/v4 in /plugins/scheduler-k3s
chore(deps): bump github.com/moby/spdystream in /plugins/scheduler-k3s
chore(deps): bump github.com/go-openapi/jsonpointer
chore(deps): bump k8s.io/api in /plugins/scheduler-k3s
Release 0.37.8
chore(deps): bump k8s.io/apimachinery in /plugins/scheduler-k3s
chore(deps): bump k8s.io/kubernetes in /plugins/scheduler-k3s
chore(deps): bump packaging from 26.0 to 26.1 in /docs/_build
chore(deps): bump github.com/fluxcd/pkg/kustomize
chore(deps): bump zipp from 3.23.0 to 3.23.1 in /docs/_build
fix(deps): pin controller-runtime to v0.22.4 for keda compatibility
chore(deps): bump github.com/cert-manager/cert-manager
chore: bump dependencies in tests/apps/php
chore: upgrade traefik from v2.11.41 to v2.11.42
chore: bump go modules
chore(deps): bump mvdan.cc/sh/v3 from 3.13.0 to 3.13.1 in /plugins/cron
chore: bump dependencies in tests/apps/multi
chore: bump go modules
chore: bump go modules
...
* master: (68 commits)
chore(deps): bump werkzeug in /tests/apps/python-flask
chore(deps): bump github.com/go-jose/go-jose/v4
chore(deps): bump rack from 3.2.5 to 3.2.6 in /tests/apps/ruby
chore(deps): bump pymdown-extensions in /docs/_build
Add application/graphql-response+json to nginx gzip_types
chore(deps): bump google.golang.org/grpc in /tests/apps/gogrpc
chore(deps): bump pygments from 2.19.2 to 2.20.0 in /docs/_build
chore(deps): bump golang.org/x/crypto in /plugins/common
chore: bump go modules
chore(deps): bump github.com/fatih/color in /plugins/common
chore(deps): bump brace-expansion in /tests/apps/multi
chore(deps): bump gunicorn in /tests/apps/python-flask
chore(deps): bump path-to-regexp in /tests/apps/checks-root
chore(deps): bump gunicorn in /tests/apps/dockerfile-release
chore(deps): bump traefik in /plugins/traefik-vhosts
chore(deps): bump gunicorn from 25.2.0 to 25.3.0 in /tests/apps/multi
chore(deps): bump werkzeug in /tests/apps/python-flask
chore(deps): bump picomatch from 2.3.1 to 2.3.2 in /tests/apps/multi
chore(deps): bump gunicorn in /tests/apps/dockerfile-release
chore(deps): bump djangorestframework in /tests/apps/dockerfile-release
...
* master: (427 commits)
chore(deps): bump gunicorn from 25.0.2 to 25.1.0 in /tests/apps/multi
chore(deps): bump flask from 3.1.2 to 3.1.3 in /tests/apps/multi
chore(deps): bump python in /docs/_build
chore(deps): bump google.golang.org/grpc in /tests/apps/gogrpc
chore(deps): bump qs from 6.14.1 to 6.14.2 in /tests/apps/checks-root
chore(deps): bump rack from 3.2.4 to 3.2.5 in /tests/apps/ruby
fix: call correct function for limiting letsencrypt to certain domains
chore(deps): bump gunicorn in /tests/apps/python-flask
chore(deps): bump whitenoise in /tests/apps/dockerfile-release
chore(deps): bump phusion/baseimage from noble-1.0.2 to noble-1.0.3
chore(deps): bump actions/upload-artifact from 6 to 7
chore(deps): bump actions/download-artifact from 7 to 8
chore(deps): bump github.com/traefik/traefik/v2
chore(deps): bump gunicorn in /tests/apps/dockerfile-release
chore(deps): bump flask from 3.1.2 to 3.1.3 in /tests/apps/python-flask
chore(deps): bump traefik from 3.6.7 to 3.6.9 in /plugins/traefik-vhosts
chore(deps): bump mkdocs-material from 9.7.1 to 9.7.3 in /docs/_build
chore(deps): bump byjg/easy-haproxy in /plugins/haproxy-vhosts
chore(deps): bump dj-database-url in /tests/apps/dockerfile-release
chore(deps): bump werkzeug in /tests/apps/python-flask
...
# Conflicts:
# common.mk
# contrib/dependencies.json
-#3144:@josegonzalez Increase curl max timeout to 600 seconds (10 minutes)
-#3107:@znz Remove unused password in ssl cert generation
-#3095:@josegonzalez Migrate env for NGINX_* to PROXY_*
-#3095:@josegonzalez Migrate env for `NGINX_*` to `PROXY_*`
-#3140:@josegonzalez Add newline to config:get output
-#3139:@josegonzalez Add ability to initialize a git repository out of band
-#3111:@josegonzalez Upgrade herokuish to 0.4.0
@@ -9251,7 +9619,7 @@ Release 0.5.5 is mostly a documentation release, further clarifying how our defa
We've also added a small section to the [dokku homepage](https://dokku.com/docs/) that lists the current core team. Feel free to look at their beautiful faces and imagine yourself contributing to Dokku and joining our core team. There are [quite a few ways to contribute](https://github.com/dokku/dokku/blob/master/CONTRIBUTING.md) - even without code/documentation - so feel more than free to jump on the bandwagon!
Finally, we've started an [Official Dokku Blog](https://dokku.github.io/), where we will post about dokku internals, roadmaps, potential use-cases, etc. An rss feed is available [here](https://dokku.github.io/feed.xml).
Finally, we've started an [Official Dokku Blog](https://dokku.github.io/), where we will post about dokku internals, roadmaps, potential use-cases, etc. An [rss feed](https://dokku.github.io/feed.xml) is available.
Thanks to all the contributors who helped with this release!
@@ -9456,7 +9824,7 @@ Thanks to all the contributors who helped with this release!
## 0.4.12
This is a small bugfix release, which will be the final release before the 0.5.x line. You can follow along on bugs/features we hope to cleanup for 0.5.x [here](https://github.com/dokku/dokku/milestones/v0.5.0).
This is a small bugfix release, which will be the final release before the 0.5.x line. You can follow along on bugs/features we hope to cleanup in the [0.5.x milestone](https://github.com/dokku/dokku/milestones/v0.5.0).
Thanks to all the contributors who helped with this release!
@@ -9955,7 +10323,7 @@ This release is a bugfix release covering dokku packaging.
## 0.3.23
This release is a bugfix release mostly covering installation and nginx issues. As well, we launched a nicer documentation site [here](https://dokku.com/docs/). Thanks to all of our contributors for making this a great release!
This release is a bugfix release mostly covering installation and nginx issues. As well, we launched a [nicer documentation site](https://dokku.com/docs/). Thanks to all of our contributors for making this a great release!
You can then proceed to configure your server domain (via `dokku domains:set-global`) and user access (via `dokku ssh-keys:add`) to complete the installation.
@@ -25,6 +25,10 @@ Every build is identified by a sortable base36 ULID-style id (`DOKKU_BUILD_ID`)
Output is also tagged into syslog as `dokku-<build-id>` so `journalctl -t dokku-<build-id>` continues to work. The on-disk log file is the durable source of truth and is read for `builds:output` even when journald has rotated old entries away.
A deploy that exits early - for example when the build fails - still finalizes its record as `failed` with its exit code.
A run that builds nothing leaves no record behind. This includes a deploy refused because the app was locked via `apps:lock`. `git:sync` has to fetch before it can tell whether there is anything to deploy, so it opens a record up front and discards it again when it turns out there is not - a `git:sync` with no `--build` flag, or a `--build-if-changes` poll that finds the remote unchanged. Without that, an app polled on a timer would accumulate `running` records that are later reaped as spurious failures, pushing real deploys out of the retention window.
### Record schema
```json
@@ -186,7 +190,7 @@ Available flags:
The following flags surface in `builds:report` but are not managed by `builds:set` - they are derived metadata recorded during the build:
| Flag | Description |
|---|---|
|--- | ---|
| `--build-id` | Unique identifier of the most recent build for the app |
| `--build-kind` | Whether the record was a `build` or a `deploy` |
| `--build-status` | Display status (`running`, `succeeded`, `failed`, `canceled`, `abandoned`); computed at read time |
datastore:app-links <datastore-type> [<app>] # list all datastore service links for a given app
datastore:backup <datastore-type> <service> <bucket-name> [--id <id>]... # create a backup of the datastore service to an existing s3 bucket
datastore:backup-auth <datastore-type> <service> <aws-access-key-id> <aws-secret-access-key>... # set up authentication for backups on the datastore service
datastore:backup-deauth <datastore-type> <service> [--id <id>] # remove backup authentication for the datastore service
datastore:backup-logs <datastore-type> <service> [-t|--tail [<tail-num>]] # print the most recent output of the scheduled backups of the service
datastore:backup-schedule <datastore-type> <service> <schedule> <bucket-name>... # schedule a backup of the datastore service
datastore:backup-schedule-cat <datastore-type> <service> [--id <id>] # cat the crontab line of a scheduled backup for the service
datastore:backup-set-encryption <datastore-type> <service> <passphrase> [--id <id>] # set encryption for all future backups of datastore service
datastore:backup-set-public-key-encryption <datastore-type> <service> <public-key-id> [--id <id>] # set GPG Public Key encryption for all future backups of datastore service
datastore:backup-unschedule <datastore-type> <service> [--id <id>|--all] # unschedule the backup of the datastore service
datastore:backup-unset-encryption <datastore-type> <service> [--id <id>] # unset encryption for future backups of the datastore service
datastore:backup-unset-public-key-encryption <datastore-type> <service> [--id <id>] # unset GPG Public Key encryption for future backups of the datastore service
datastore:clone <datastore-type> <service> <new-service> [--clone-flags...] # create container <new-name> then copy data from <name> into <new-name>
datastore:connect <datastore-type> <service> # connect to the service via the <datastore-type> connection tool
datastore:create <datastore-type> <service> [--create-flags...] # create a datastore service
datastore:create-database <datastore-type> <service> <database> # create a database in the datastore service
datastore:create-user <datastore-type> <service> <user> [<password>] # create a user in the datastore service
datastore:destroy <datastore-type> <service> [-f|--force] # delete the datastore service/data/container if there are no links left
datastore:destroy-database <datastore-type> <service> <database> [-f|--force] # destroy a database created in the datastore service
datastore:destroy-user <datastore-type> <service> <user> [-f|--force] # destroy a user created in the datastore service
datastore:enter <datastore-type> <service> # enter or run a command in a running datastore service container
datastore:exists <datastore-type> <service> # check if the datastore service exists
datastore:export <datastore-type> <service> [-f|--file <path>] [--force] # export a dump of the datastore service database
datastore:expose <datastore-type> <service> <ports...> # expose a datastore service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified)
datastore:help [<datastore-type> [<command>]] # print the help for every datastore, one datastore or one of its commands
datastore:import <datastore-type> <service> [-f|--file <path>] # import a dump into the datastore service database
datastore:info <datastore-type> [<service>] [--info-flags...] # print the service information
datastore:label <datastore-type> <service> <key=value>... # add docker labels to the service container
datastore:link <datastore-type> <service> [<app>] [--link-flags...] # link the datastore service to the app
datastore:linked <datastore-type> <service> [<app>] # check if the datastore service is linked to an app
datastore:links <datastore-type> <service> # list all apps linked to the datastore service
datastore:list <datastore-type> # list all datastore services
datastore:logs <datastore-type> <service> [-t|--tail [<tail-num>]] # print the most recent log(s) for this service
datastore:mount <datastore-type> [--replace] <service> <source:container-dir[:options]>... # mount a host path or docker volume into the service container
datastore:pause <datastore-type> <service> # pause a running datastore service
datastore:promote <datastore-type> <service> [<app>] # promote service <service> as DATABASE_URL in <app>
datastore:reexpose <datastore-type> <service> # reexpose a datastore service, applying its expose settings
datastore:reset <datastore-type> <service> [-f|--force] # delete all data in the datastore service, keeping the service and its links
datastore:restart <datastore-type> <service> # graceful shutdown and restart of the datastore service container
datastore:set <datastore-type> <service> <key> [<value>]... # set or clear a property for a service
datastore:set-user-password <datastore-type> <service> <user> [<password>]... # change the password of a user created in the datastore service
datastore:start <datastore-type> <service> # start a previously stopped datastore service
datastore:stop <datastore-type> <service> # stop a running datastore service
datastore:unexpose <datastore-type> <service> # unexpose a previously exposed datastore service
datastore:unlabel <datastore-type> [--all] <service> [<key>...] # remove one or all docker labels from the service container
datastore:unlink <datastore-type> <service> [<app>] [-n|--no-restart] # unlink the datastore service from the app
datastore:unmount <datastore-type> [--all] <service> [<source:container-dir>...] # remove one or all mounts from the service container
datastore:upgrade <datastore-type> <service> [--upgrade-flags...] # upgrade service <service> to the specified versions
datastore:version # print the version of dokku-datastore the plugin runs
```
The `datastore` plugin manages datastore services such as postgres, mysql and redis, and links them to apps. Every command takes the datastore type as its first argument, and the datastores it serves are the ones the [official datastore plugins](/docs/community/plugins.md#official-plugins) provide: clickhouse, couchdb, elasticsearch, graphite, mariadb, meilisearch, memcached, mongo, mysql, nats, omnisci, postgres, pushpin, rabbitmq, redis, rethinkdb, solr and typesense.
The plugin is built from [dokku-datastore](https://github.com/dokku/dokku-datastore), which the official datastore plugins also run, and keeps its services in the same place they do, under `/var/lib/dokku/services`.
## Usage
### Creating and linking a service
A service is created with `datastore:create` and linked to an app with `datastore:link`, which sets the variable the app connects with, such as `DATABASE_URL` for postgres or `REDIS_URL` for redis, and restarts the app.
Deleting an app unlinks it from every service it is linked to.
### Getting help
`datastore:help` lists the datastore types and the commands they share. Not every datastore implements every command, so the help for one datastore lists only the commands it implements, including ones only it has, such as `connect-admin` for mongo. The help for one of a datastore's commands describes its flags and shows examples.
```shell
dokku datastore:help
dokku datastore:help postgres
dokku datastore:help postgres create
```
### Scheduled backups
A backup scheduled with `datastore:backup-schedule` is added to the dokku crontab as a `dokku datastore:backup` command, and is managed with the [cron plugin](/docs/processes/scheduled-cron-tasks.md) like any other scheduled task.
Installing dokku does not pull the image of every datastore. A datastore's images are pulled the first time a command needs them, and on later installs and upgrades once the datastore has a service.
### Official datastore plugins
The official datastore plugins, such as [dokku-postgres](https://github.com/dokku/dokku-postgres), may be installed alongside the `datastore` plugin. While one is enabled, the `datastore` plugin leaves that datastore to it when dokku runs plugin triggers, such as unlinking an app that is being deleted or writing scheduled backups into the crontab. Commands for that datastore should be run through the official plugin until it is removed.
The `datastore` plugin does not read the datastore definitions an official plugin ships, so a host running a modified official plugin should keep using it.
#### Migrating from an official datastore plugin
Moving a datastore from its official plugin to the `datastore` plugin leaves its services, containers, data, settings and links where they are, and does not stop running services or the apps linked to them. Using postgres as an example:
1. Check that `dokku datastore:version` is at least the version dokku-postgres runs, which is printed by `/var/lib/dokku/data/postgres/dokku-datastore version`. If the official plugin runs a newer version, wait for a dokku release that ships it.
2. Uninstall the official plugin.
```shell
dokku plugin:uninstall postgres
```
3. Run every plugin's install trigger, which sets the host up for postgres, brings its services up to date and rewrites the crontab so scheduled backups run through `dokku datastore:backup`.
```shell
dokku plugin:install
```
4. Change anything that runs `dokku postgres:<command> <service>` to run `dokku datastore:<command> postgres <service>`.
To move back, install the official plugin again from a release running at least the version `dokku datastore:version` prints.
The value is the path to the desired file *relative* to the base search directory, and will never be treated as absolute paths in any context. If that file does not exist within the repository, Dokku will continue the build process as if the repository has no `app.json`.
The value is the path to the desired file _relative_ to the base search directory, and will never be treated as absolute paths in any context. If that file does not exist within the repository, Dokku will continue the build process as if the repository has no `app.json`.
The default value may be set by passing an empty value for the option:
@@ -142,7 +142,7 @@ app2.json
#### `app.json` deployment tasks
Dokku provides limited support for the `app.json` manifest from Heroku (documentation available [here](https://devcenter.heroku.com/articles/app-json-schema)). The keys available for use with Deployment Tasks are:
Dokku provides limited support for the `app.json` manifest from Heroku (see the [app.json schema documentation](https://devcenter.heroku.com/articles/app-json-schema)). The keys available for use with Deployment Tasks are:
-`scripts.dokku.predeploy`: This is run _after_ an app's docker image is built, but _before_ any containers are scheduled. Changes made to your image are committed at this phase.
-`scripts.dokku.postdeploy`: This is run _after_ an app's containers are scheduled. Changes made to your image are _not_ committed at this phase.
@@ -34,7 +34,7 @@ Adding or removing docker-options will not apply to any running containers, and
#### Supported Docker Options
More information on supported Docker options can be found [here](https://docs.docker.com/engine/reference/commandline/run/).
More information on supported Docker options can be found in the [docker run reference](https://docs.docker.com/engine/reference/commandline/run/).
Container options configured via the `docker-options` plugin are not used to modify the process a container runs. Container options are the `[OPTIONS]` portion of the following, where `[CONTAINER_COMMAND]` and `[ARG]` are the process and the arguments passed to it that are launched in the created container: `docker run [OPTIONS] [CONTAINER_COMMAND] [ARG...]`. Please see the documentation for [customizing the run command](/docs/deployment/builders/dockerfiles.md#customizing-the-run-command) or use a [Procfile](/docs/deployment/builders/dockerfiles.md#procfiles-and-multiple-processes) to modify the command used by a Dockerfile-based container.
The following properties are recorded internally by the docker-options plugin and are not exposed via `docker-options:report`:
| Property | Scope | Description | Source |
|---|---|---|---|
|--- | --- | --- | ---|
| `migrated-from-files` | global | Global migration sentinel that records that the legacy `DOCKER_OPTIONS_<PHASE>` flat-file store has been drained into plugin properties | `plugins/docker-options/functions.go` writes `"true"` once the install-time migration runs |
| `migrated-build` | per-app | Per-app marker recording that the app's legacy `DOCKER_OPTIONS_BUILD` file was drained into the `_default_.build` property list. Only set when the legacy file contained non-empty content | `plugins/docker-options/functions.go` writes `"true"` after the per-phase drain |
| `migrated-deploy` | per-app | Per-app marker recording that the app's legacy `DOCKER_OPTIONS_DEPLOY` file was drained into the `_default_.deploy` property list. Only set when the legacy file contained non-empty content | `plugins/docker-options/functions.go` writes `"true"` after the per-phase drain |
storage:report [<app>|--global] [<flag>] # Displays a storage report for one or more apps
storage:set <name> <property> [<value>] # Update a storage entry in place
storage:unmount [--all] <app> [<host-dir:container-dir>...] [flags] # Remove one or all bind mounts
storage:wait <name> # Wait for a storage entry's PVC to be bound (k3s)
```
A storage entry is the source of truth for the underlying volume - a host directory on docker-local, or a PersistentVolumeClaim on k3s. Multiple apps can mount the same entry, and an attachment carries the per-app details (container path, phases, subpath, readonly, process type). Names are globally unique across the install and must be DNS-1123 labels (lowercase letters, digits, dashes) of 45 characters or less so they can be used verbatim as Helm release and PVC names.
@@ -89,6 +88,8 @@ Each entry mirrors the underlying attachment. `readonly` (boolean) and `volume_o
A storage directory can be created with the `storage:ensure-directory` command. This command will create a subdirectory in the recommended `/var/lib/dokku/data/storage` path - created during Dokku installation - and prepare it for use with an app.
When Dokku itself runs in a container with a shared Docker socket, default named storage is still created under `DOKKU_LIB_ROOT` (typically `/var/lib/dokku`) inside the Dokku container. Docker `-v` mounts for `storage:exec` and app deploys rewrite that path to `DOKKU_LIB_HOST_ROOT` so the host Docker daemon binds the directory from the host-visible data volume. See the [Docker installation notes](/docs/getting-started/install/docker.md) for setting `DOKKU_LIB_HOST_ROOT`.
In the first example, Dokku will then mount the shared contents of `/var/lib/dokku/data/storage/node-js-app` to `/app/storage` inside the container. The mount point is *not* relative to your app's working directory, and is instead relative to the root (`/`) of the container. Mounts are only available for containers created via `run` and by the deploy process, and not during the build process. In addition, the host path is never auto-created by either Dokku or Docker, and should be an explicit path, not one relative to the current working directory.
In the first example, Dokku will then mount the shared contents of `/var/lib/dokku/data/storage/node-js-app` to `/app/storage` inside the container. The mount point is *not* relative to your app's working directory, and is instead relative to the root (`/`) of the container. Mounts are only available for containers created via `run` and by the deploy process, and not during the build process. A mount is bound into every process type unless `--process-type` narrows it - see [scoping a mount to a process type](#scoping-a-mount-to-a-process-type). In addition, the host path is never auto-created by either Dokku or Docker, and should be an explicit path, not one relative to the current working directory.
> If the `/storage` path within the container had pre-existing content, the container files will be over-written. This may be an issue for users that create assets at build time but then mount a directory at the same place during runtime. Files are not merged.
@@ -269,9 +270,101 @@ Once persistent storage is mounted, the app requires a restart. See the [process
dokku ps:restart app-name
```
#### Scoping a mount to a process type
> [!IMPORTANT]
> New as of 0.38.29
By default a mount is bound into every one of an app's containers. `--process-type` narrows it to a single Procfile process type, which is how a worker gets a scratch volume the web process has no business seeing:
The magic `_default_` process type is the unscoped case, and it is what a mount gets when `--process-type` is omitted. Default-scoped mounts are bound into every process; a mount scoped to a named process type is bound only into that one.
Some containers belong to no process type at all - a `dokku run` one-off, the ephemeral containers that run `app.json` deploy tasks, and k3s cron jobs. Those receive default-scoped mounts only, since there is no process type for a named scope to match. On k3s this applies to the deploy phase; `--phase run` attachments are not mounted there at all.
A container path can be claimed by only one mount per process type, so `storage:mount` rejects a container path that an overlapping scope already holds. The default scope overlaps every named one:
> New as of 0.38.29. Per-mount attributes in the option list new as of 0.38.30.
The form above mounts one entry at a time, so matching an app's mounts to a declared set means reading `storage:list`, computing the difference in both directions and issuing one `storage:mount` or `storage:unmount` per addition and removal - with a failure partway through leaving the app holding a mixture of the two sets. The `--replace` flag writes the whole set in a single call, taking `name:container-dir` pairs instead of a single entry and a `--container-dir` flag.
Anything previously mounted and not named in the call is unmounted. Each pair takes the same optional third field as the legacy colon form, a comma-separated list, and that list carries every mount-time attribute the mount needs, so two mounts declared in one call can differ in all of them:
| `rw` | Mount it read-write, the explicit opposite of `ro` and the default |
| `volume-subpath=<path>` | Subpath within the entry |
| `volume-chown=<option>` | Chown option applied at mount time |
| `phase=<deploy\|run>` | Phase to mount in, repeated once per phase; both phases when omitted |
| anything else | A Docker mount option, passed through verbatim in the order given |
Order within the list does not matter, and the keys mirror the flags the single-entry form uses so each attribute has exactly one spelling. Phases are stored in `deploy,run` order however they were written, so re-issuing an equivalent call converges rather than registering a change. A token value cannot contain a comma, since the comma is what separates tokens; a subpath needing one is set with the single-entry form. Note that `storage:list` renders the Docker `-v` view and omits the subpath, chown and phases, so its output does not round-trip into `--replace` - `storage:report` shows every field.
A pair whose first field starts with `/` is a legacy host path rather than an entry name, and registers its `legacy-<hash>` entry the same way the colon form does. Any other first field must name a registered entry, so a mistyped name is rejected rather than taken for a docker volume. A docker volume already mounted through the colon form is named here by the `legacy-<hash>` entry that `storage:list-entries` shows. Only the first field decides which entry a host path resolves to, so adding tokens to a pair never moves it onto a different one.
Because each attribute has exactly one spelling in this form, `--phase`, `--volume-subpath`, `--volume-readonly`, `--volume-chown` and `--volume-options` are rejected with `--replace` rather than acting as call-level defaults a token overrides:
! The --volume-subpath flag cannot be used with --replace; set volume-subpath=<path> in the mount spec instead
```
The single-entry form keeps all five flags.
`--process-type` is the exception and scopes the replacement the same way it scopes a single mount, since it selects which mounts the call is authoritative over rather than describing any one of them. Omitting it replaces the `_default_` process type, leaving mounts scoped to a named process type in place:
dokku storage:mount --replace node-js-app node-js-cache:/cache --process-type web
```
Every pair is parsed and validated before anything is written, so a rejected pair leaves the stored mounts untouched. A pair without a container directory, a container directory that is not absolute, an entry that is not registered, an entry whose scheduler does not match the app's, and a container directory named more than once in one call are all rejected. So are the option list's own mistakes: an unknown key, a key given more than once, an empty value, an empty option, `ro` and `rw` together, a phase that is neither `deploy` nor `run`, and a chown value the plugin does not understand. Rejecting an unknown key is what catches a typo like `volume-subpth=uploads`, which would otherwise be stored as a Docker mount option and surface only when the next deploy failed.
> [!NOTE]
> An empty pair list is rejected rather than treated as a request to unmount everything, so a generated list that expands to nothing cannot silently drop an app's storage. Use `storage:unmount --all` for that.
As with a single mount, the app requires a restart afterward.
### Unmounting storage
If an app no longer requires a mounted volume or directory, the `storage:unmount` command can be called. This takes the same arguments as the `storage:mount` command, an app name and a `host-path:container-path` or `docker-volume:container-path` combination.
If an app no longer requires a mounted volume or directory, the `storage:unmount` command can be called. This takes the same arguments as the `storage:mount` command, an app name and a `host-path:container-path` or `docker-volume:container-path` combination, or a registered entry name.
```shell
# unmount the directory from your container's /app/storage directory, relative to the container root (/)
> Removing more than one attachment at a time is new as of 0.38.29
More than one attachment can be removed in a single call, naming each by entry name or by the colon form. Every argument is resolved against the app's attachments before any of them is removed, so an argument naming an attachment the app does not have removes none of them:
An entry mounted at several container directories is disambiguated with `--container-dir`, which therefore applies only when a single entry is named. Naming the container directory in the argument itself works with any number of attachments:
The `--all` flag removes every attachment on an app. This is the empty case `storage:mount --replace` refuses to handle, and it takes no entry arguments:
```shell
dokku storage:unmount --all node-js-app
```
`--process-type` narrows what is removed to a single scope, filtering the same way it scopes a replacement. Omitting it removes every scope, and a scope that matches nothing is a no-op rather than an error, so the command is safe to re-run:
```shell
dokku storage:unmount --all node-js-app --process-type web
```
Once persistent storage is unmounted, the app requires a restart. See the [process scaling documentation](/docs/processes/process-management.md) for more information.
Indices are assigned in stored order, so `storage:mount --replace` renumbers them: the attachments it leaves in place keep their relative order and the declared ones follow.
A single attachment field can be fetched directly via the info-flag form:
The list can also be emitted as JSON via the `--format json` flag. In addition to the name, version, enabled state, and description shown in the default output, the JSON output includes whether a plugin is a core plugin and - for git-based third-party plugins - the install source (the git remote URL, the currently checked-out commit, and the followed branch). This is useful for tooling that reconstructs a server's set of installed plugins:
@@ -77,7 +77,7 @@ dokku plugin:list --format json
[
{
"name":"apps",
"version":"0.38.27",
"version":"0.38.34",
"enabled":true,
"core":true,
"description":"dokku core apps plugin",
@@ -249,6 +249,8 @@ dokku plugin:enable postgres
-----> Plugin postgres enabled
```
Core plugins that were not enabled at install time - such as the nginx-vhosts plugin when the `dokku/nginx_enable` debconf option is set to `false` - can also be enabled this way. Run `dokku plugin:install --core` afterwards so the plugin's install trigger is executed.
### Triggering a plugin trigger
The `plugin:trigger` can be used to call any internal plugin trigger. This may have unintended consequences, and thus should only be called for development or debugging purposes.
registry:login [--global|--password-stdin] [<app>] <server> <username> [<password>] # Login to a docker registry
registry:logout [--global] [<app>] <server> # Logout from a docker registry
registry:report [<app>] [<flag>] # Displays a registry report for one or more apps
registry:set <app>|--global <key> (<value>)# Set or clear a registry property for an app
registry:auth-status [--password-stdin] <app>|--global <server> [<username> [<password>]] # Reports whether the stored registry credential matches the requested state
registry:login [--global|--password-stdin] [<app>] <server> <username> [<password>] # Login to a docker registry
registry:logout [--global] [<app>] <server> # Logout from a docker registry
registry:report [<app>] [<flag>] # Displays a registry report for one or more apps
registry:set <app>|--global <key> (<value>) # Set or clear a registry property for an app
```
The registry plugin enables interacting with remote registries, which is useful when either deploying images via `git:from-image` or when interacting with custom schedulers to deploy built image artifacts.
When an app is destroyed, any per-app registry credentials are automatically removed.
### Checking the configured auth state
> [!IMPORTANT]
> New as of 0.38.29
The `registry:auth-status` command reports whether a stored registry credential matches a desired state without exposing it. This allows external tooling such as configuration management systems to converge idempotently, rather than re-running `registry:login` on every pass to discover that nothing needed to change.
```shell
# check whether an app is configured with the expected credentials
The result is reported via the exit code, and nothing is printed when the state could be determined:
| Exit code | Description |
| --------- | ----------- |
| `0` | The stored credential matches the requested state. |
| `1` | There is no stored credential for the specified server. |
| `2` | There is a stored credential for the specified server, but it does not match the requested state. |
| `3` | The command was called with invalid arguments and the state could not be checked. |
| `4` | A credential is stored for the specified server, but it cannot be compared. |
| `20` | The specified app does not exist. |
The distinction between `1` and `2` allows a caller to tell a server that needs a credential created apart from a server whose existing credential would be replaced, without issuing a second call.
When called without a username, the requested state is that no credential exists for the server. An existing credential is therefore reported as `2`, and `1` is never returned. This is the check to perform before deciding whether a `registry:logout` is necessary.
Exit code `4` is returned when a credential exists but Dokku cannot read the secret it holds - either because a docker credential helper is configured for that server and stores the secret outside of `config.json`, or because the registry issued an identity token in place of a password. In both cases a username that differs from the one supplied is still reported as `2`, so `4` means only that an otherwise matching credential could not be confirmed. Callers should treat `4` as "unknown" rather than retrying the login indefinitely.
Unlike `registry:login`, the `--global` flag is required in order to check the global credentials - the deprecated implicit-global form is not accepted. A per-app check reads only the credentials created by `registry:login <app>` and does not fall back to the global credentials, so an app configured with a credential identical to the global one is still reported as needing one of its own. To see which credentials would actually be used for an app, read the `computed-auth-servers` field of `registry:report`.
> [!WARNING]
> A `0` exit code means the stored credential matches the one supplied, not that it still authenticates against the registry. A password that has been revoked upstream compares equal here and fails at push or pull time.
### Setting a remote server
To specify a remote server registry for pushes, set the `server` property via the `registry:set` command. The default value for this property is empty string. Setting the value to `docker.io` or `hub.docker.com` will result in the computed value being empty string (as that is the default, implicit registry), while any non-zero length value will have a `/` appended to it if there is not one already.
When `push-on-release` is enabled, Dokku treats the remote registry as the canonical store for app images. If a local image disappears - for example because a `docker image prune` cron ran, the host rebooted, or an operator removed it manually - subsequent commands that need the image (`ps:restart`, `ps:scale`, `dokku run`, `domains:add`, `certs:add`, etc.) will pull the missing tag back from the registry automatically. Per-app registry credentials configured via `registry:login` are honored during the pull. This recovery covers the deployed numeric tag pushed by the registry plugin - a missing `latest` tag will be ignored.
### Push extra tags
### Push extra tags
To push the image on release with extra tags, set the `push-extra-tags` to a comma-separated list of tags via the `registry:set` command. The default value for this property is empty. Setting the property will result in the image being tagged with extra tags every release.
@@ -204,6 +251,7 @@ To push the image on release with extra tags, set the `push-extra-tags` to a com
> The `Report flags` column lists the CLI argument names accepted by `registry:report`. The JSON keys emitted by `registry:report --format json` are the same names with the leading `--registry-` stripped (e.g. `image-repo`, `global-server`, `computed-push-on-release`). Legacy keys with the `registry-` prefix (e.g. `registry-image-repo`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `image-repo` | app only | `dokku/<app>` | `--registry-image-repo`, `--registry-computed-image-repo` | Repository name used when pushing the app's image (overrides the global template) |
| `image-repo-template` | app + global | none | `--registry-image-repo-template`, `--registry-global-image-repo-template`, `--registry-computed-image-repo-template` | Go template used to compute the per-app image repository when `image-repo` is unset |
| `push-extra-tags` | app + global | none | `--registry-push-extra-tags`, `--registry-global-push-extra-tags`, `--registry-computed-push-extra-tags` | Comma-separated list of additional tags pushed alongside the deploy tag |
| `push-on-release` | app + global | `false` | `--registry-push-on-release`, `--registry-global-push-on-release`, `--registry-computed-push-on-release` | When `true`, pushes the image to the registry on every successful build |
| `server` | app + global | none | `--registry-server`, `--registry-global-server`, `--registry-computed-server` | Registry server host (e.g. `ghcr.io`) used when pushing images |
### Read-only report fields
> [!IMPORTANT]
> New as of 0.38.29
The following `registry:report` fields describe credentials created by `registry:login` and are not settable via `registry:set`.
| Report flag | Scope | Description |
| --- | --- | --- |
| `--registry-auth-servers` | app only | Comma-separated list of servers the app has its own credentials for |
| `--registry-global-auth-servers` | app + global | Comma-separated list of servers the global credentials cover |
| `--registry-computed-auth-servers` | app + global | Comma-separated list of servers whose credentials would be used for the app |
Servers are listed under the name they would be passed to `registry:login`, so a Docker Hub credential is reported as `docker.io` rather than the index server address docker stores it under. Only the server names are reported; no part of a credential is ever emitted.
Docker is pointed at one config directory or the other rather than a merge of the two, so an app with any credential of its own shadows the global credentials entirely. The computed value is therefore the app's own list when it has one and the global list otherwise, rather than a per-server merge of the two.
@@ -35,3 +35,5 @@ Building containers with buildpacks currently results in a persistent `cache` di
```shell
dokku repo:purge-cache node-js-app
```
To purge the cache and rebuild the app in a single command, see the [rebuilding without the build cache](/docs/processes/process-management.md#rebuilding-without-the-build-cache) documentation.
resource:limit [--process-type <process-type>] [RESOURCE_OPTS...] <app> # Limit resources for a given app/process-type combination
resource:limit-clear [--process-type <process-type>] <app> # Limit resources for a given app/process-type combination
resource:limit-clear [--process-type <process-type>] <app> # Clear resource limits for a given app/process-type combination
resource:report [<app>] [<flag>] # Displays a resource report for one or more apps
resource:reserve [--process-type <process-type>] [RESOURCE_OPTS...] <app> # Reserve resources for a given app/process-type combination
resource:reserve-clear [--process-type <process-type>] <app> # Reserve resources for a given app/process-type combination
resource:reserve-clear [--process-type <process-type>] <app> # Clear resource reservations for a given app/process-type combination
```
The resource plugin is meant to allow users to limit or reserve resources for a given app/process-type combination.
@@ -30,6 +30,8 @@ Valid resource options include:
-`--network-egress`
-`--nvidia-gpu`
Values for `--memory` and `--memory-swap` are validated when set, and invalid values are rejected.
See the [Supported Resource Management Properties](/docs/deployment/schedulers/docker-local.md#supported-resource-management-properties) section of the docker local scheduler documentation for more information on how each resource limit maps to Docker.
Resource reservations are applied only during the `run` and `deploy` phases of an application. Resource limits also apply during the `build` phase when explicitly configured against the `build` process type - see [Build-time Resource Limits](#build-time-resource-limits) below.
@@ -156,24 +158,24 @@ Resource keys outside the supported set for a builder are silently ignored. Rese
#### Clearing Resource Limits
In cases where the values are incorrect - or there is no desire to limit resources - resource limits may be cleared using the `resource:limit-clear` command.
In cases where the values are incorrect - or there is no desire to limit resources - resource limits may be cleared using the `resource:limit-clear` command. When no process type is specified, resource limits are cleared for all process types. Resource reservations are left untouched.
```shell
dokku resource:limit-clear node-js-app
```
```
-----> Clearing resource limit for node-js-app
=====> clearing node-js-app limits
```
Defaults can also be cleared by leaving the app unspecified.
Resource limits for a single process type can be cleared by specifying the `--process-type` flag.
@@ -264,24 +266,24 @@ dokku resource:reserve --process-type web node-js-app
#### Clearing Resource Reservations
In cases where the values are incorrect - or there is no desire to reserve resources - resource reservations may be cleared using the `resource:reserve-clear` command.
In cases where the values are incorrect - or there is no desire to reserve resources - resource reservations may be cleared using the `resource:reserve-clear` command. When no process type is specified, resource reservations are cleared for all process types. Resource limits are left untouched.
```shell
dokku resource:reserve-clear node-js-app
```
```
-----> Clearing resource reservation for node-js-app
=====> clearing node-js-app reservation
```
Defaults can also be cleared by leaving the app unspecified.
Resource reservations for a single process type can be cleared by specifying the `--process-type` flag.
- The 1.0.0 release of Dokku will no longer select buildpack builders over dockerfile builders if both builders match. Instead, Dokku will choose the first builder that responds to the `builder-detect` trigger. Users that wish to use a specific builder may set a builder using the `builder:set` command, which will force Dokku to use the specified builder regardless of what might be auto-detected.
- The `tags` plugin is deprecated in favor of the [`git:from-image`](/docs/deployment/methods/git.md#initializing-an-app-repository-from-a-docker-image) command. It will be removed in the next minor release, and is considered unmaintained. Users are highly encouraged to switch their workflows to `git:from-image`.
- The `tar` plugin is deprecated in favor of the [`git:from-archive`](/docs/deployment/methods/git.md#initializing-an-app-repository-from-an-archive-file) command. It will be removed in the next minor release, and is considered unmaintained. Users are highly encouraged to switch their workflows to `git:from-archive`.
- The `tags` plugin is deprecated in favor of the [`git:from-image`](/docs/deployment/methods/image.md#initializing-an-app-repository-from-a-docker-image) command. It will be removed in the next minor release, and is considered unmaintained. Users are highly encouraged to switch their workflows to `git:from-image`.
- The `tar` plugin is deprecated in favor of the [`git:from-archive`](/docs/deployment/methods/archive.md#initializing-an-app-repository-from-an-archive-file) command. It will be removed in the next minor release, and is considered unmaintained. Users are highly encouraged to switch their workflows to `git:from-archive`.
@@ -24,7 +24,7 @@ Before upgrading, uninstall the registry plugin via `dokku plugin:uninstall regi
### Deprecations
- In previous versions of Dokku, the only way to specify a custom `Dockerfile` was to use the `docker-options` plugin to set the `--file` flag for a docker build. As of 0.25.0, the `builder-dockerfile:set` command should be used instead, as outlined in the [docs here](/docs/deployment/builders/dockerfiles.md#changingthe-dockerfile-location). Usage of the old method should be migrated to the new method.
- In previous versions of Dokku, the only way to specify a custom `Dockerfile` was to use the `docker-options` plugin to set the `--file` flag for a docker build. As of 0.25.0, the `builder-dockerfile:set` command should be used instead, as outlined in the [docs here](/docs/deployment/builders/dockerfiles.md#changing-the-dockerfile-location). Usage of the old method should be migrated to the new method.
- The `--rm` and `--rm-container` flags may be specified but no longer have any effect on `dokku run`.
- The `--detach` flag is deprecated in favor of the `run:detached` command.
- The `DOKKU_SCALE` file is deprecated. Please see the [process management documentation](/docs/processes/process-management.md#manually-managing-process-scaling) for more information on it's replacement with the `formation` key of the `app.json` file.
- The default image used for Cloud Native Buildpack builds is now `heroku/builder:24`, and is based on both the `heroku-24` stack as well as Ubuntu 24.04. Users that wish to stick with the old, `heroku-22`/Ubuntu 22.04 builder may specify `heroku/buildpacks:22` as their builder image. Please see the [cloud native buildpack deployment documentation](/docs/deployment/builders/cloud-native-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder.
- The default image used for Herokuish Buildpack builds is now `gliderlabs/herokuish:latest-24`, and is based on both the `heroku-24` stack as well as Ubuntu 24.04. Users that wish to stick with the old, `heroku-22`/Ubuntu 22.04 builder may specify `gliderlabs/herokuish:latest-22` as their builder image. Please see the [herokuish buildpack deployment documentation](/docs/deployment/builders/herokuish-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder.
- The `vector` image used by Dokku for enhanced logging support has been upgraded from 0.39.x to 0.41.x. To take advantage of the new image, execute the following commands:
```
dokku logs:vector-stop
dokku logs:vector-start
```
- The `openresty` image used by Dokku for proxying via openresty has been upgraded from 0.8.0 to 0.9.0. To take advantage of the new image, execute the following commands:
@@ -45,7 +45,7 @@ A number of `DOKKU_*` config environment variables have been replaced with prope
Going forward, configure these settings using the property commands listed below. Setting the deprecated env vars via `dokku config:set` will no longer have any effect.
| Deprecated Env Var | Replacement Command |
|---|---|
|--- | ---|
| `DOKKU_APP_PROXY_TYPE` | `dokku proxy:set <app> type <value>` |
- The buildpack `stack` property has been split out of the `buildpacks` plugin and into the two builder plugins that consume it. Previously a single `stack` value set via `buildpacks:set-property` was shared by both the herokuish and CNB (`pack`) builders, even though a herokuish stack image is not a valid CNB builder and vice versa. The herokuish stack is now managed via `builder-herokuish:set <app> stack <value>` (default `gliderlabs/herokuish:latest-24`), and the CNB builder is managed via `builder-pack:set <app> stack <value>` (default `heroku/builder:24`). Both support the `--global` scope and expose `stack`, `global-stack`, and `computed-stack` keys via their respective `:report` commands.
- The `buildpacks:set-property stack` command is deprecated but continues to work. It emits a deprecation warning and forwards the value to the appropriate builder: values referencing a herokuish image (the value contains `herokuish`) are written to `builder-herokuish`, and all other values are written to `builder-pack`. Unsetting (`buildpacks:set-property <app> stack`) clears the value on both builders.
- The `--buildpacks-stack`, `--buildpacks-global-stack`, and `--buildpacks-computed-stack` report flags (and the corresponding `stack`, `global-stack`, `computed-stack` JSON keys) have been removed from `buildpacks:report`. Use `builder-herokuish:report` and `builder-pack:report` instead. The `buildpacks:report` command continues to report the buildpacks `list`.
- The internal `buildpack-stack-name` plugin trigger has been removed. Each builder now reads its own `stack` property directly during the build.
- Changing a builder's `stack` now clears that builder's build cache so the next deploy starts fresh. For herokuish this removes the `cache-<app>` Docker volume; for pack the next `pack build` runs with `--clear-cache`.
- The `dokku` Debian package now declares `coredns-docker (>= 0.8.0)` as a hard `Depends:`. `apt install dokku` and `apt upgrade dokku` pull it in automatically. The deb ships a default `/etc/coredns/Corefile` that binds `127.0.0.1:1053`, serves the `docker.` zone, and synthesizes `<app>.<process-type>.docker` and `<app>.docker``A` records from the `com.dokku.app-name` and `com.dokku.process-type` labels Dokku already stamps on every deployed container. The Docker image of Dokku ships coredns-docker preinstalled and runs it under runit on the same port.
- The bundled `nginx.conf.sigil` can now use an nginx `resolver` directive and a variable-based `proxy_pass` against `<app>.<process-type>.docker` instead of pinning the upstream to a literal IP. This addresses the long-standing post-reboot and post-`apt-upgrade-docker` stale-vhost bug ([#8313](https://github.com/dokku/dokku/issues/8313)) - the rendered upstream re-resolves at request time via coredns-docker, so a host reboot or single-container restart no longer leaves nginx routing to a stale IP. This behavior is opt-in, and upstreams remain IP-based unless `dns-resolver` is set:
See the [nginx documentation](/docs/networking/proxies/nginx.md#upstream-resolution-via-coredns-docker) for details.
- Three new nginx properties control the resolver path:
- `dns-resolver` (default: none) - the address nginx talks to. Resolver-based upstreams are only rendered when this is set.
- `dns-resolver-timeout` (default: `5s`) - how long nginx waits for a DNS response before giving up and returning 502. The short default keeps clients from waiting on nginx's built-in 30s timeout when coredns-docker is unreachable.
- `dns-zone` (default: `docker`) - the suffix appended to `<app>.<process-type>` when constructing the upstream hostname.
All three can be set per app or globally via `dokku nginx:set`.
- Vendored `nginx.conf.sigil` templates are unaffected by the change. The existing `DOKKU_APP_<PROC>_LISTENERS` variable continues to be populated with the IP-based listener list exactly as before, so templates that only reference it continue to render IP-based upstreams regardless of the new properties. To opt a vendored template into the resolver path, switch its `proxy_pass` line to use the new `DOKKU_APP_<PROC>_LISTENER_HOST`, `NGINX_DNS_RESOLVER`, `NGINX_DNS_RESOLVER_TIMEOUT`, and `NGINX_DNS_ZONE` sigil variables - see the [nginx.conf.sigil reference](/docs/appendices/file-formats/nginx-conf-sigil.md#upstream-rendering-ip-based-vs-dns-based) for the exact snippet.
### Stack property migrated to builder plugins
Existing `stack` values stored via `buildpacks:set-property` are migrated automatically the first time `dokku` runs the buildpacks plugin install step after the upgrade. Each per-app and global value is moved to `builder-herokuish` or `builder-pack` based on the same herokuish detection used by the deprecated command, and the old `buildpacks` value is removed. No manual action is required.
Going forward, configure the stack using the builder commands:
- The nginx-vhosts template language is now [sigil](https://github.com/gliderlabs/sigil)
- No need to escape literal `$` characters (or other "bash-isms")
- Template variables are represented as {{ .VARIABLE_NAME }}
- A detailed list of template variables can be found [here](/docs/networking/proxies/nginx.md#available-template-variables)
- A detailed list of template variables can be found in the [available template variables documentation](/docs/networking/proxies/nginx.md#available-template-variables)
- A custom nginx-vhosts template must be named `nginx.conf.sigil`
- The default path for this custom template is the root of your repo (i.e. `/app` in the container or `WORKDIR` if defined in a dockerfile app)
- Dokku no longer looks for this file in `/home/dokku/node-js-app` on the Dokku server
- Check out an example template [here](/docs/networking/proxies/nginx.md)
- Check out an [example template](/docs/networking/proxies/nginx.md)
- Support for server-wide SSL certs have been dropped in favor of using the `certs` plugin
-`dokku certs:add node-js-app < certs.tar`
- All domains for an SSL-enabled app will be redirected to https by default
@@ -90,6 +90,7 @@ Environment variables from `app.json` are processed during the first deploy, bef
4.**Optional variables without a value**: Skipped silently if no TTY is available
On subsequent deploys:
- Variables are NOT re-set unless `sync: true` is specified
- Variables with `sync: true` are always set to their configured value, overwriting any manual changes
- Variables that already have values are not modified
@@ -97,6 +98,7 @@ On subsequent deploys:
### Examples
**Simple default value:**
```json
{
"env":{
@@ -106,6 +108,7 @@ On subsequent deploys:
```
**Generated secret (recommended for API keys, tokens, etc.):**
```json
{
"env":{
@@ -118,6 +121,7 @@ On subsequent deploys:
```
**Required variable that must be provided:**
```json
{
"env":{
@@ -130,6 +134,7 @@ On subsequent deploys:
```
**Variable that stays in sync with app.json:**
```json
{
"env":{
@@ -158,7 +163,7 @@ On subsequent deploys:
-`autoscaling` (map of string to object, optional) autoscaling rules. See the autoscaling section for more details
-`max_parallel`: (int, optional) number of instances to deploy in parallel at a given time
-`quantity`: (int, optional) number of processes to maintain. Default 1 for web processes, 0 for all others.
-`quantity`: (int, optional) number of processes to maintain. Must be zero or greater. Default 1 for web processes, 0 for all others.
-`service`: (map of string to oject, optional) governs how non-web processes are exposed as services on the network
### Autoscaling
@@ -244,9 +249,9 @@ An autoscaling trigger consists of the following properties:
-`initialDelay`: (int, optional)
-`listening`: (boolean, optional)
-`name`: (string, optional)
-`path`: (string, optional)
-`path`: (string, optional) Must start with a `/` and must not contain whitespace, control characters, quotes, or backslashes. Deploys with an invalid `path` will fail.
-`port`: (int, optional)
-`scheme`: (string, optional)
-`scheme`: (string, optional) Must be either `http` or `https`. Deploys with an invalid `scheme` will fail.
The `nginx.conf.sigil` file is used to configure the nginx server for an application. The default template can be found [here](https://github.com/dokku/dokku/blob/master/plugins/nginx-vhosts/templates/nginx.conf.sigil). Dokku uses a tool named [sigil](https://github.com/gliderlabs/sigil) to generate the nginx configuration based on the template provided.
The `nginx.conf.sigil` file is used to configure the nginx server for an application. The [default template](https://github.com/dokku/dokku/blob/master/plugins/nginx-vhosts/templates/nginx.conf.sigil) is in the nginx-vhosts plugin. Dokku uses a tool named [sigil](https://github.com/gliderlabs/sigil) to generate the nginx configuration based on the template provided.
## Validation
A custom `nginx.conf.sigil` is pre-validated at the start of every deploy, immediately after it is extracted from the source tree and before the build phase runs. Pre-validation renders the template via sigil with the same parameters used at deploy time, wraps the rendered config in a minimal `events`/`http` scaffold, and runs `nginx -t` against the result. The deploy is aborted if either the sigil render or the `nginx -t` check fails, so build work is not wasted on a syntactically invalid template. When no app listeners exist yet (typical for first deploys), pre-validation injects a placeholder`127.0.0.1:5000` listener for `DOKKU_APP_WEB_LISTENERS` so that the rendered upstream block has a static server entry and `nginx -t` does not bail out on "host not found in upstream".
A custom `nginx.conf.sigil` is pre-validated at the start of every deploy, immediately after it is extracted from the source tree and before the build phase runs. Pre-validation renders the template via sigil with the same parameters used at deploy time, wraps the rendered config in a minimal `events`/`http` scaffold, and runs `nginx -t` against the result. The deploy is aborted if either the sigil render or the `nginx -t` check fails, so build work is not wasted on a syntactically invalid template. When no app listeners exist yet (typical for first deploys), pre-validation injects placeholders so the rendered config parses cleanly under`nginx -t`:
-`DOKKU_APP_WEB_LISTENERS` is set to `127.0.0.1:5000` so the legacy `upstream` block has a static server entry.
-`DOKKU_APP_WEB_LISTENER_HOST` is set to `127.0.0.1` so the resolver-based `set $dokku_upstream "...";` line renders to a syntactically valid value.
Pre-validation is skipped when the proxy type is not `nginx` or when `disable-custom-config` is set to `true` for the app.
The wrapper declares the same `user` directive as the host's main nginx config, exposed to the wrapper template as `$.NGINX_USER`. This matters because `nginx -t` runs as `root` and chowns every directory the config registers - in particular any `proxy_cache_path` - to whichever user the config resolves to, whether or not that directory already exists. Without a matching `user`, validating a template that declares a `proxy_cache_path` would hand the running server's live cache directory to nginx's compile-time default user for the length of the build, and every request served from or stored in that cache would fail with a permission error until the deploy's own nginx reload restored the ownership.
### Custom nginx modules
Pre-validation runs `nginx -t` against a minimal wrapper config that does _not_ include the top-level `load_module` directives from the global `/etc/nginx/nginx.conf`. A `nginx.conf.sigil` that uses a directive provided by a dynamically loaded module - such as `image_filter`, provided by the [ngx_http_image_filter_module](https://nginx.org/en/docs/http/ngx_http_image_filter_module.html) - therefore fails pre-validation with an `unknown directive` error, even though `nginx -t` succeeds against the real server config where the module is loaded.
@@ -35,6 +40,7 @@ The custom `validate.conf.sigil`, which is the [default wrapper](https://github.
{{ if $.NGINX_USER }}user {{ $.NGINX_USER }};{{ end }}
events { worker_connections 768; }
http {
access_log off;
@@ -43,6 +49,8 @@ http {
}
```
A custom wrapper that omits the `user` line still has one injected before `nginx -t` runs, so wrappers written against older Dokku releases keep working, but declaring it explicitly documents the behavior and keeps the template self-contained.
The same override also governs the standalone `dokku nginx:validate-config` command, which renders the `validate-config` template as well.
Dokku logs a deprecation warning during deploys when a custom template still uses the `listen ... http2` form, so the offending template can be located via the deploy output.
## Upstream Rendering: IP-based vs DNS-based
> [!IMPORTANT]
> New as of 0.39.0
The bundled `nginx.conf.sigil` supports two rendering paths for the upstream, and both sets of sigil variables are populated on every render. Vendored templates can use either path independently of the global `dns-resolver` setting.
### IP-based upstream (legacy path)
The classic form uses `DOKKU_APP_<PROC>_LISTENERS`: a space-separated `<ip>:<port>` list, one entry per scaled instance of the process type. The IPs come from `/home/dokku/<APP>/IP.<proc>.<index>` and the ports from `/home/dokku/<APP>/PORT.<proc>.<index>`. A typical use is an `upstream` block with one `server <ip>:<port>;` per instance:
This path renders the IP that Dokku saw when the config was last built. After a host reboot, an `apt upgrade docker` without live-restore, or a single container restart, those IPs can become stale until `dokku proxy:build-config` runs again. The DNS-based path below addresses that staleness.
### DNS-based upstream (resolver path)
The new variables (`DOKKU_APP_<PROC>_LISTENER_HOST` + `NGINX_DNS_RESOLVER` + `NGINX_DNS_RESOLVER_TIMEOUT` + `NGINX_DNS_ZONE`) describe a single hostname per process type of the form `<app>.<proctype>.<dns-zone>` (for example, `myapp.web.docker`). The hostname is served by [coredns-docker](https://github.com/dokku/coredns-docker) (>= `0.8.0`), which watches Docker events and collapses every running instance of the process type onto one multi-A record set. Combined with an nginx `resolver` directive and a variable-based `proxy_pass`, the upstream re-resolves at request time honoring DNS TTL - which gives runtime IP refresh on container restarts and DNS round-robin across scaled instances.
The `$dokku_upstream` variable name is intentionally namespaced (rather than `$upstream`) so vendored templates that already define `$upstream` will not collide if they adopt the resolver branch.
`NGINX_DNS_RESOLVER` is empty by default, `NGINX_DNS_RESOLVER_TIMEOUT` defaults to `5s`, and `NGINX_DNS_ZONE` defaults to `docker`; all three are configurable via `dokku nginx:set --global dns-resolver ...`, `dokku nginx:set --global dns-resolver-timeout ...`, and `dokku nginx:set --global dns-zone ...`. The bundled template only renders the resolver branch when `NGINX_DNS_RESOLVER` is set, for example to `127.0.0.1:1053`. Unsetting `dns-resolver` switches the bundled template back to the IP-based upstream. The short default `dns-resolver-timeout` keeps clients from waiting on nginx's built-in 30s timeout when coredns-docker is unreachable; nginx returns 502 after the configured interval instead.
### Choosing between the two
The IP-based path is the default for the bundled template, and the DNS-based path is used once `dns-resolver` is set. Vendored templates that have not been updated continue to use `DOKKU_APP_<PROC>_LISTENERS` and are unaffected by the `dns-resolver` setting. The `DOKKU_APP_<PROC>_LISTENER_HOST` and `NGINX_DNS_ZONE` variables are populated regardless of whether `dns-resolver` is set, so a vendored template can adopt the resolver path on its own schedule by switching its rendering snippet, even if other apps on the host are still using IP-based upstreams.
@@ -22,6 +22,8 @@ Let's take a quick look at the current Dokku nginx plugin that's shipped with Do
The following plugins are available and provided by Dokku maintainers. Please file issues against their respective issue trackers.
Every datastore below is also served by the `datastore` plugin Dokku ships, as in `dokku datastore:create postgres lollipop`. See the [datastore management documentation](/docs/advanced-usage/datastore-management.md) for more information.
If the environment cannot be written to disk, `config:set`, `config:unset` and `config:clear` report the failure and exit non-zero without restarting the application. The previous environment is left intact, so the command can be retried once the underlying problem - a full disk or the wrong permissions on `$DOKKU_LIB_ROOT/config/<app>` being the usual causes - has been dealt with.
When setting or unsetting environment variables, you may wish to avoid an application restart. This is useful when developing plugins or when setting multiple environment variables in a scripted manner. To do so, use the `--no-restart` flag:
```shell
@@ -136,6 +138,9 @@ The following config variables have special meanings and can be set in a variety
| `DOKKU_EVENTS_LOGFILE` | `$DOKKU_LOGS_DIR/events.log` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | Where the events log file is written to. |
| `DOKKU_APP_NAME` | none | `--app APP` flag | Name of application to work on. Respected by core plugins. |
| `DOKKU_APPS_FORCE_DELETE` | none | `--force` flag | Whether to force delete an application. Also used by other plugins for destructive actions. |
| `DOKKU_DISABLE_TTY` | none | `dokku run --no-tty` <br /> environment | Forces `dokku run` to treat the session as non-interactive. |
| `DOKKU_FORCE_TTY` | none | `dokku run --force-tty` <br /> environment | Forces `dokku run` to treat the session as interactive. |
| `DOKKU_PRESERVE_ENV` | none | `/etc/default/dokku` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` <br /> environment | Comma-separated list of extra environment variable names to carry across the privilege drop to the `dokku` user. See [Forwarding environment variables](#forwarding-environment-variables). |
| `DOKKU_CHECKS_URL` | `https://dokku.com/docs/deployment/zero-downtime-deploys/` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | Url displayed during deployment when no CHECKS file exists. |
| `DOKKU_QUIET_OUTPUT` | none | `--quiet` flag | Silences certain header output for `dokku` commands. |
| `DOKKU_RM_CONTAINER` | none | `dokku config:set` <br /> | Deprecated: Whether to keep `dokku run` containers around or not. |
@@ -143,6 +148,45 @@ The following config variables have special meanings and can be set in a variety
| `DOKKU_SYSTEM_GROUP` | `dokku` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | System group to chown files as. |
| `DOKKU_SYSTEM_USER` | `dokku` | `/etc/environment` <br /> `~dokku/.dokkurc` <br /> `~dokku/.dokkurc/*` | System user to chown files as. |
## Forwarding environment variables
When `dokku` is invoked by any user other than `dokku` - `root`, or a member of the `sudo` group - it
re-executes itself as the `dokku` user. Most configuration does not need to survive that step, since
`/etc/default/dokku`, `~dokku/dokkurc` and `~dokku/.dokkurc/*` are read again by the re-executed
process, and `/etc/environment` is applied by PAM.
What cannot be recomputed is state that belongs to the invocation itself, so those variables are
named explicitly and forwarded:
| Variable | Source |
| --- | --- |
| `DOKKU_APP_NAME`, `DOKKU_APPS_FORCE_DELETE`, `DOKKU_QUIET_OUTPUT`, `DOKKU_TRACE` | the `--app`, `--force`, `--quiet` and `--trace` global flags, or the environment |
| `DOKKU_GLOBAL_FLAGS` | every global flag as given, replayed by plugins that need them |
| `DOKKU_DISABLE_TTY`, `DOKKU_FORCE_TTY` | the `dokku run` tty flags, or the environment |
| `DOKKU_PRESERVE_ENV` | the list below |
| `SSH_USER`, `SSH_NAME`, `NAME`, `FINGERPRINT`, `SSH_ORIGINAL_COMMAND` | the ssh session the command arrived on |
| `DOCKER_BIN`, `DOKKU_CNB_BUILDER`, `DOKKU_EVENTS_LOGFILE`, `DOKKU_HOST_ROOT`, `DOKKU_IMAGE`, `DOKKU_LIB_PATH`, `DOKKU_LOGS_DIR`, `DOKKU_LOGS_HOST_DIR`, `DOKKU_ROOT`, `DOKKU_SYSTEM_GROUP`, `DOKKU_SYSTEM_USER`, `PLUGIN_PATH`, `PLUGIN_AVAILABLE_PATH`, `PLUGIN_ENABLED_PATH`, `PLUGIN_CORE_PATH`, `PLUGIN_CORE_AVAILABLE_PATH`, `PLUGIN_CORE_ENABLED_PATH` | the environment, so the re-executed process derives the same paths as the caller |
| `DOKKU_INIT_SYSTEM`, `DOKKU_LIB_HOST_ROOT` | the environment, which is where they live while a Docker image is being built |
`DOKKU_PID` is deliberately not forwarded: it is unset before the privilege drop so the re-executed
process gets its own.
To forward anything else - a variable read by a third-party plugin, or a one-off override of a
setting that normally lives in `/etc/default/dokku` - name it in `DOKKU_PRESERVE_ENV`:
Ssl issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO RSA Domain Validation Secure Server CA
Ssl serial: 2F4B8C1D6E9A0357B8C2D4E6F8091A2B
Ssl starts at: Oct 5 00:00:00 2016 GMT
Ssl subject: OU=Domain Control Validated; OU=PositiveSSL Wildcard; CN=*.dokku.org
Ssl verified: self signed
```
You can pass flags which will output only the value of the specific information you want. For example:
@@ -149,7 +155,7 @@ If an `https:443:*` port mapping already exists when a certificate is installed
### Running behind a proxy (`X-Forwarded-Ssl`, etc.)
See the [running behind another proxy documentation](/docs/networking/proxies/nginx.md#running-behind-another-proxy--configuring-x-forwarded--headers) for more information on how to configure your Nginx config when your server is running behind a proxy (e.g. load balancer, etc.).
See the [running behind another proxy documentation](/docs/networking/proxies/nginx.md#running-behind-another-proxy-configuring-x-forwarded-headers) for more information on how to configure your Nginx config when your server is running behind a proxy (e.g. load balancer, etc.).
## Properties
@@ -158,12 +164,14 @@ See the [running behind another proxy documentation](/docs/networking/proxies/ng
The following flags surface in `certs:report` but are not managed by `certs:set` - they are derived from the on-disk certificate:
| Flag | Description |
|---|---|
|--- | ---|
| `--ssl-enabled` | `true` when an SSL certificate is installed for the app |
| `--ssl-dir` | Absolute path to the per-app certificate directory |
| `--ssl-hostnames` | Hostnames the certificate covers (CN plus Subject Alternative Names) |
| `--ssl-issuer` | Certificate issuer DN |
| `--ssl-subject` | Certificate subject DN |
| `--ssl-verified` | `true` if the certificate chain verifies against the system CA bundle |
| `--ssl-fingerprint` | SHA-256 digest of the leaf certificate's DER encoding, the value `openssl x509 -noout -fingerprint -sha256` prints |
| `--ssl-serial` | Serial number of the leaf certificate |
| `--ssl-verified` | `verified by a certificate authority` when the certificate chain verifies against the system CA bundle, `self signed` otherwise |
Dokku by default **does not** provide datastores (e.g. MySQL, PostgreSQL) on a newly created app. You can add datastore support by installing plugins, and the Dokku project [provides official plugins](/docs/community/plugins.md#official-plugins-beta) for common datastores.
Dokku by default **does not** provide datastores (e.g. MySQL, PostgreSQL) on a newly created app. You can add datastore support by installing plugins, and the Dokku project [provides official plugins](/docs/community/plugins.md#official-plugins) for common datastores.
The Getting Started app requires a PostgreSQL service, so install the plugin and create the related service as follows:
@@ -108,7 +108,7 @@ Dokku supports deploying applications in a few ways:
- [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) via [Herokuish](https://github.com/gliderlabs/herokuish#buildpacks): See the [herokuish buildpacks documentation](/docs/deployment/builders/herokuish-buildpacks.md) to learn about the different ways to specify a buildpack.
- This is the default method used by Dokku.
- [Dockerfile](https://docs.docker.com/reference/builder/): See the [dockerfile documentation](/docs/deployment/builders/dockerfiles.md) to learn about the different ways to configure Dockerfile-based deploys.
- [Docker Image](https://docs.docker.com/get-started/overview/#docker-objects): See the [docker image documentation](/docs/deployment/methods/git.md#initializing-an-app-repository-from-a-docker-image) to learn about how to deploy a Docker Image.
- [Docker Image](https://docs.docker.com/get-started/overview/#docker-objects): See the [docker image documentation](/docs/deployment/methods/image.md#initializing-an-app-repository-from-a-docker-image) to learn about how to deploy a Docker Image.
### Setting up SSL
@@ -246,7 +246,7 @@ See the [Dockerfile documentation](/docs/deployment/builders/dockerfiles.md) for
## Image tagging
See the [image tagging documentation](/docs/deployment/methods/git.md#initializing-an-app-repository-from-a-docker-image) for more information on how Docker images can be tagged and deployed for a given application.
See the [image tagging documentation](/docs/deployment/methods/image.md#initializing-an-app-repository-from-a-docker-image) for more information on how Docker images can be tagged and deployed for a given application.
While an app is locked, deploys that build the app - `git push`, `ps:rebuild`, `git:sync`, `git:from-archive`, `git:from-image`, and `git:load-image` - fail without building or fetching any code, and leave no build record behind. A deploy that is already in progress when the lock is created is not interrupted. Commands that redeploy an existing image, such as `ps:restart`, `ps:start`, `config:set`, and `deploy`, are not blocked by the lock.
A locked app cannot be renamed via `apps:rename` until it is unlocked, and an app created via `apps:clone` does not inherit the lock of the app it was cloned from.
The deploy lock taken by a deploy is released when the deploy finishes, including when the build fails or the deploy process exits early.
### Unlocking app deploys
> [!IMPORTANT]
@@ -263,7 +269,7 @@ dokku apps:unlock node-js-app
> [!IMPORTANT]
> New as of 0.13.0
In some cases, you may wish to inspect the state of an app lock. To do so, you can issue an `apps:lock` command. This will exit non-zero if there is no app lock in place.
In some cases, you may wish to inspect the state of an app lock. To do so, you can issue an `apps:locked` command. This will exit non-zero if there is no app lock in place. An app is considered locked if it was locked via `apps:lock` or if a deploy is currently in progress.
@@ -167,6 +167,8 @@ To create a custom builder, the following triggers must be implemented:
- arguments: `BUILDER_TYPE``APP``IMAGE_AG`
- description: A post-build, pre-release trigger that can be used to post-process the image. Usually simply tags and labels the image appropriately.
When an app is rebuilt via `ps:rebuild --no-cache`, the `DOKKU_BUILD_NO_CACHE` environment variable is set to `true` during the `builder-build` trigger. Custom builders should skip any build cache they use when this variable is set.
Custom plugins names _must_ have the prefix `builder-` or builder overriding via `builder:set` may not function as expected.
Builders can use any tools available on the system to build the docker image, and may even be used to schedule building off-server. The only current requirement is that the image must exist on the server at the end of the `builder-build` command, though this requirement may be relaxed in a future release.
@@ -181,7 +183,7 @@ For a simple example of how to implement this trigger, see `builder-pack`, which
> The `Report flags` column lists the CLI argument names accepted by `builder:report`. The JSON keys emitted by `builder:report --format json` are the same names with the leading `--builder-` stripped (e.g. `selected`, `global-selected`, `computed-selected`). Legacy keys with the `builder-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `build-dir` | app + global | none | `--builder-build-dir`, `--builder-global-build-dir`, `--builder-computed-build-dir` | Subdirectory within the repository to use as the build context |
| `skip-cleanup` | app + global | `false` | `--builder-skip-cleanup`, `--builder-global-skip-cleanup`, `--builder-computed-skip-cleanup` | When `true`, leaves intermediate build artifacts in place after the build |
These properties are managed via `buildpacks:set-property` (the legacy command name for this plugin).
The`buildpacks` plugin has no settable properties of its own. The list of buildpack URLs is managed via the `buildpacks:add`, `buildpacks:set`, `buildpacks:remove`, and `buildpacks:clear` commands.
> [!NOTE]
> The `Report flags` column lists the CLI argument names accepted by `buildpacks:report`. The JSON keys emitted by `buildpacks:report --format json` are the same names with the leading `--buildpacks-` stripped (e.g. `stack`, `global-stack`, `computed-stack`). Legacy keys with the `buildpacks-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `stack` | app + global | none | `--buildpacks-stack`, `--buildpacks-global-stack`, `--buildpacks-computed-stack` | Herokuish stack image used to compile the app (e.g. `heroku/heroku:24`) |
> [!WARNING]
> The `buildpacks:set-property stack` command is deprecated as of 0.39.0. The `stack` property moved to the `builder-herokuish` and `builder-pack` plugins. Set it via `builder-herokuish:set <app> stack <value>` (herokuish) or `builder-pack:set <app> stack <value>` (CNB). The deprecated command still works but emits a warning and forwards the value to the appropriate builder based on whether the value references a herokuish image.
@@ -35,7 +35,7 @@ While pack is well-supported in Dokku, there are a number of caveats. Please not
- A future release will add support for injecting extra arguments during the build process.
- The default process type is `web`.
- Build cache is stored in Docker volumes instead of on disk. As such, `repo:purge-cache` currently has no effect.
-A future version will add integration with the `repo` plugin.
-The build cache may be cleared for a single rebuild via `ps:rebuild --no-cache`. See the [rebuilding without the build cache](/docs/processes/process-management.md#rebuilding-without-the-build-cache) documentation for more information.
-`pack` is not currently included with Dokku, nor is it added as a package dependency.
- A future version will include it as a package dependency.
The `projecttoml-path` and `global-projecttoml-path` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-projecttoml-path` key holds the effective value used at build time, falling back to the global value (where one has been set) and then to the built-in default of `project.toml`.
@@ -138,32 +147,35 @@ project2.toml
### Customizing the Buildpack stack builder
> [!IMPORTANT]
> New as of 0.23.0
> New as of 0.23.0. The `stack` property moved from the `buildpacks` plugin to the `builder-pack` plugin in 0.39.0.
The default stack builder in use by CNB buildpacks in Dokku is based on `heroku/builder:24`. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `buildpacks:set-property` command.
The default stack builder in use by CNB buildpacks in Dokku is based on `heroku/builder:24`. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `builder-pack:set` command.
The specified stack builder can also be unset by omitting the name of the stack builder when calling `buildpacks:set-property`.
The specified stack builder can also be unset by omitting the name of the stack builder when calling `builder-pack:set`.
```shell
dokku buildpacks:set-property node-js-app stack
dokku builder-pack:set node-js-app stack
```
A change in the stack builder value will execute the`post-stack-set` trigger.
A change in the stack builder value flags the next build to run with `--clear-cache` so a stale pack cache is not reused.
Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_CNB_BUILDER` environment variable (`heroku/builder:24` by default).
> The `buildpacks:set-property stack` command is deprecated. It still works but emits a deprecation warning and forwards the value to `builder-pack` (when the value is a CNB builder) or `builder-herokuish` (when the value references a herokuish image).
### Specifying commands via Procfile
See the [Procfile documentation](/docs/processes/process-management.md#procfile) for more information on how to specify different processes for your app.
@@ -175,3 +187,12 @@ See the [Procfile documentation](/docs/processes/process-management.md#procfile)
| `projecttoml-path` | app + global | `project.toml` | `--builder-pack-projecttoml-path`, `--builder-pack-global-projecttoml-path`, `--builder-pack-computed-projecttoml-path` | Path within the app to the CNB `project.toml` manifest, relative to the build root |
| `stack` | app + global | `heroku/builder:24` | `--builder-pack-stack`, `--builder-pack-global-stack`, `--builder-pack-computed-stack` | CNB builder image used to build the app (e.g. `paketobuildpacks/build:base-cnb`) |
### Internal properties
The following properties are recorded internally by the builder-pack plugin and are not exposed via `builder-pack:report`:
| Property | Scope | Description | Source |
|---|---|---|---|
| `clear-cache` | per-app | Transient flag set when the pack `stack` changes. The next `pack build` runs with `--clear-cache` and then the flag is deleted, ensuring a stack change does not reuse a stale pack cache | `plugins/builder-pack/subcommands/set` sets it on stack change; `plugins/builder-pack/builder-build` consumes and deletes it |
> New as of 0.23.0. The `stack` property moved from the `buildpacks` plugin to the `builder-herokuish` plugin in 0.39.0.
The default stack builder in use by Herokuish buildpacks in Dokku is based on `gliderlabs/herokuish:latest`. Typically, this is installed via an OS package which pulls the requisite Docker image. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `buildpacks:set-property` command.
The default stack builder in use by Herokuish buildpacks in Dokku is based on `gliderlabs/herokuish:latest`. Typically, this is installed via an OS package which pulls the requisite Docker image. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `builder-herokuish:set` command.
The specified stack builder can also be unset by omitting the name of the stack builder when calling `buildpacks:set-property`.
The specified stack builder can also be unset by omitting the name of the stack builder when calling `builder-herokuish:set`.
```shell
dokku buildpacks:set-property node-js-app stack
dokku builder-herokuish:set node-js-app stack
```
A change in the stack builder value will execute the `post-stack-set` trigger.
A change in the stack builder value clears the herokuish build cache so the next deploy starts fresh.
Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_IMAGE` environment variable (`gliderlabs/herokuish:latest-24` by default).
> The `buildpacks:set-property stack` command is deprecated. It still works but emits a deprecation warning and forwards the value to `builder-herokuish` (when the value references a herokuish image) or `builder-pack` (otherwise).
The `allowed` and `global-allowed` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-allowed` key holds the effective value used at build time, falling back to the global value (where one has been set) and then to the built-in default (`true` on amd64, `false` otherwise).
See the [repository management documentation](/docs/advanced-usage/repository-management.md#clearing-app-cache) for more information on how to clear buildpack build cache for an application.
See the [repository management documentation](/docs/advanced-usage/repository-management.md#clearing-application-cache) for more information on how to clear buildpack build cache for an application. The cache may also be purged as part of a rebuild via `ps:rebuild --no-cache`. See the [rebuilding without the build cache](/docs/processes/process-management.md#rebuilding-without-the-build-cache) documentation for more information.
### Specifying commands via Procfile
@@ -207,7 +219,6 @@ See the [buildpack management documentation](/docs/deployment/builders/buildpack
See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#using-a-specific-buildpack-version) for more information on how to using a specific buildpack version
### Displaying buildpack reports for an app
See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#displaying-buildpack-reports-for-an-app) for more information on how to display buildpack reports for an app.
@@ -218,4 +229,5 @@ See the [buildpack management documentation](/docs/deployment/builders/buildpack
| `allowed` | app + global | `true` | `--builder-herokuish-allowed`, `--builder-herokuish-global-allowed`, `--builder-herokuish-computed-allowed` | When `false`, the herokuish builder is skipped during builder detection for this app |
| `allowed` | app + global | `true` | `--builder-herokuish-allowed`, `--builder-herokuish-global-allowed`, `--builder-herokuish-computed-allowed` | When `false`, the herokuish builder is skipped during builder detection for this app |
| `stack` | app + global | `gliderlabs/herokuish:latest-24` | `--builder-herokuish-stack`, `--builder-herokuish-global-stack`, `--builder-herokuish-computed-stack` | Herokuish stack image used to compile the app (e.g. `gliderlabs/herokuish:latest`) |
To disable the cache for a single rebuild without persisting a setting, see the [rebuilding without the build cache](/docs/processes/process-management.md#rebuilding-without-the-build-cache) documentation.
### Displaying builder-nixpacks reports for an app
You can get a report about the app's storage status using the `builder-nixpacks:report` command:
@@ -13,14 +13,17 @@ Before using Railpacks, the following steps must be taken:
- Install the `railpack` cli: The `railpack` cli tool is not included by default with Dokku or as a dependency. It must also be installed as shown on [this page](https://railpack.com/installation).
- Create a `buildkit` builder: Railpack uses buildkit.
```shell
docker run --rm --privileged -d --name buildkit moby/buildkit
```
```shell
docker run --rm --privileged -d --name buildkit moby/buildkit
```
- Set the buildkit builder: Update the `/etc/default/dokku` file to set `BUILDKIT_HOST`:
To disable the cache for a single rebuild without persisting a setting, see the [rebuilding without the build cache](/docs/processes/process-management.md#rebuilding-without-the-build-cache) documentation.
### Displaying builder-railpack reports for an app
You can get a report about the app's storage status using the `builder-railpack:report` command:
- The standard `clone` stage is replaced with one that performs a complete clone of the repository (the default is a shallow clone, which dokku would reject).
- A `GIT_REMOTE_URL` and a `SSH_PRIVATE_KEY` (with help of the `from_secret` directive) suffice to carry out a deployment.
- A `GIT_REMOTE_URL` and a `SSH_PRIVATE_KEY` (with help of the `from_secret` directive) suffice to carry out a deployment.
Setting a cron sink **moves** cron task output rather than copying it. Vector routes each log line to exactly one of the two sinks:
| Configuration | Where cron output goes | Where all other output goes |
|---|---|---|
|--- | --- | ---|
| `vector-sink` only | `vector-sink` | `vector-sink` |
| `vector-cron-sink` only | `vector-cron-sink` | nowhere |
| both | `vector-cron-sink` | `vector-sink` |
@@ -398,7 +398,7 @@ The alias only changes the shipped event. Containers are always discovered by th
> The `Report flags` column lists the CLI argument names accepted by `logs:report`. The JSON keys emitted by `logs:report --format json` are the same names with the leading `--logs-` stripped (e.g. `max-size`, `global-max-size`, `computed-max-size`). Legacy keys with the `logs-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `app-label-alias` | app + global | `com.dokku.app-name` | `--logs-app-label-alias`, `--logs-global-app-label-alias`, `--logs-computed-app-label-alias` | Field name the app name is shipped under, renamed from `com.dokku.app-name` on the event |
| `max-size` | app + global | `10m` | `--logs-max-size`, `--logs-global-max-size`, `--logs-computed-max-size` | Maximum size of an individual log file before rotation |
| `vector-image` | global only | _parsed from `plugins/logs/Dockerfile`_ | `--logs-global-vector-image`, `--logs-computed-vector-image` | Docker image used to run the vector log-shipper container |
@@ -170,6 +170,8 @@ By default, when running `git:sync` with a git branch reference, Dokku will auto
dokku git:sync --skip-deploy-branch node-js-app https://github.com/heroku/node-js-getting-started.git main
```
The `git:sync` command holds the app's deploy lock while fetching, and will fail if a deploy is already in progress or the app has been locked via `apps:lock`. When the fetched code changes the repository or a build is triggered, the `source-image` property set by [image deploys](/docs/deployment/methods/image.md) is cleared. As the synced code is kept even when a build fails, the property is not restored on build failure.
The `git:auth-status` command reports whether the configured `netrc` entry matches a desired state without exposing the underlying file. It exits `0` when the configured state matches and `1` otherwise. This allows external tooling such as configuration management systems to perform idempotent updates without reading `$DOKKU_ROOT/.netrc` directly.
The `git:auth-status` command reports whether the configured `netrc` entry matches a desired state without exposing the underlying file. This allows external tooling such as configuration management systems to perform idempotent updates without reading `$DOKKU_ROOT/.netrc` directly.
```shell
# check whether github.com is configured with the expected credentials
| `0` | The configured `netrc` entry matches the requested state. |
| `1` | There is no `netrc` entry for the specified host. |
| `2` | There is a `netrc` entry for the specified host, but it does not match the requested state. |
| `3` | The command was called with invalid arguments and the state could not be checked. |
The distinction between `1` and `2` allows a caller to tell a host that needs a `netrc` entry created apart from a host whose existing credentials would be replaced, without issuing a second call.
When called without a username, the requested state is that no `netrc` entry exists for the host. An existing entry is therefore reported as `2`, and `1` is never returned.
As with `git:auth`, the password may be provided via `STDIN`:
```shell
@@ -324,7 +339,7 @@ The `--format` flag cannot be combined with an info flag.
| `archive-max-files` | app + global | none | `--git-global-archive-max-files`, `--git-computed-archive-max-files` | Maximum number of files allowed in an uploaded archive deploy |
| `archive-max-size` | app + global | none | `--git-global-archive-max-size`, `--git-computed-archive-max-size` | Maximum total size of an uploaded archive deploy |
| `deploy-branch` | app + global | `master` | `--git-deploy-branch`, `--git-global-deploy-branch`, `--git-computed-deploy-branch` | Branch name pushed by the git remote that triggers a deploy |
@@ -337,6 +352,6 @@ The `--format` flag cannot be combined with an info flag.
The following flags surface in `git:report` but are not managed by `git:set` - they are derived from repository state:
| Flag | Description |
|---|---|
|--- | ---|
| `--git-sha` | HEAD commit SHA of the app's git repo |
| `--git-last-updated-at` | UNIX timestamp of the last write to the deploy branch ref |
See the [dockerfile documentation](/docs/deployment/builders/dockerfiles.md) to learn about the different ways to configure Dockerfile-based deploys.
The image used for a `git:from-image` or `git:load-image` deploy is stored in the `source-image` git property, which can be viewed via `dokku git:report node-js-app --git-source-image`. This property is only changed while the app's deploy lock is held. If the deploy fails, the deploy branch is rolled back and the property is restored to its previous value, so it always refers to the image the repository was last successfully deployed from. Deploying via other methods - such as `git push`, `git:from-archive`, or `git:sync` - clears the property.
## Initializing an app repository from a remote image without a registry
All image containers with the label `org.opencontainers.image.vendor=linuxserver.io` will have the automatic init process injection force-disabled without further intervention when neither an app-level nor a global value is set.
### Failed deploys
When a deploy fails - for example, because a new container does not pass its [zero downtime checks](/docs/deployment/zero-downtime-deploys.md) - the previously running containers are left in place. The app's `latest` image tag is restored to the image the previously running containers use, so commands such as `ps:restart`, `ps:start`, and `ps:restore` bring back the previous release instead of the failed one. The image of the failed release is then removed by `ps:retire`.
To support this, the image an app is running is tagged as `dokku/<app>:dokku-previous` while a build and deploy are in progress. This tag is removed once the deploy completes.
The image currently tagged as the app's `latest` image is never removed by `ps:retire`. This allows an app whose first deploy failed to be started once the cause of the failure has been corrected without requiring a rebuild.
When the `registry` plugin's `push-on-release` property is enabled, the incremented image tag is kept after a failed deploy, and restarting the app will deploy that tag.
### Deploying Process Types in Parallel
> [!IMPORTANT]
@@ -205,16 +215,16 @@ The `docker-local` scheduler supports a minimal list of resource _limits_ and _r
- cpu: (docker option: `--cpus`), is specified in number of CPUs a process can access.
- See the ["CPU" section](https://docs.docker.com/config/containers/resource_constraints/#cpu) of the Docker Runtime Options documentation for more information.
- memory: (docker option: `--memory`) should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes). Default unit is `m` (megabytes).
- memory: (docker option: `--memory`) should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes), `t` (terabytes) or `p` (petabytes). Suffixes are case-insensitive and may be followed by `i` and/or `b`, such as `512M`, `1G`, or `1GiB`. Default unit is `m` (megabytes).
- See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information.
- memory-swap: (docker option: `--memory-swap`) should be specified with a suffix of `b` (bytes),`k`(kilobytes), `m` (megabytes), `g` (gigabytes)
- memory-swap: (docker option: `--memory-swap`) should be specified with the same suffixes as `memory`, or`-1`for unlimited swap. Values without a suffix are in bytes.
- See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information.
- nvidia-gpus: (docker option: `--gpus`), is specified in number of Nvidia GPUs a process can access.
- See the ["GPU" section](https://docs.docker.com/config/containers/resource_constraints/#gpu) of the Docker Runtime Options documentation for more information.
#### Resource Reservations
- memory: (docker option: `--memory-reservation`) should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes). Default unit is `m` (megabytes).
- memory: (docker option: `--memory-reservation`) should be specified with the same suffixes as the `memory` limit. Default unit is `m` (megabytes).
- See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information.
## Properties
@@ -222,6 +232,6 @@ The `docker-local` scheduler supports a minimal list of resource _limits_ and _r
| `init-process` | app + global | `true` | `--scheduler-docker-local-init-process`, `--scheduler-docker-local-global-init-process`, `--scheduler-docker-local-computed-init-process` | When `true`, runs containers with Docker's `--init` flag (reaping zombie processes) |
| `parallel-schedule-count` | app + global | `1` | `--scheduler-docker-local-parallel-schedule-count`, `--scheduler-docker-local-global-parallel-schedule-count`, `--scheduler-docker-local-computed-parallel-schedule-count` | Maximum number of containers scheduled in parallel during a deploy |
scheduler-k3s:annotations:clear <app|--global> [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Clear all annotations for an app or a single process-type/resource-type scope
scheduler-k3s:annotations:set <app|--global> <property> (<value>) [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE> # Set or clear an annotation for a given app/process-type/resource-type combination
scheduler-k3s:annotations:set --replace <app|--global> <key=value> [<key=value> ...] [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE> # Replace the entire annotation map for a given app/process-type/resource-type combination
scheduler-k3s:annotations:report [<app>|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Displays a scheduler-k3s annotations report for one or more apps
scheduler-k3s:autoscaling-auth:set <app|--global> <trigger> [<--metadata key=value>...] # Set or clear a scheduler-k3s autoscaling keda trigger authentication resource for an app
scheduler-k3s:autoscaling-auth:set --replace <app|--global> <trigger> <--metadata key=value> [<--metadata key=value>...] # Replace the entire metadata map for a scheduler-k3s autoscaling keda trigger authentication resource
scheduler-k3s:autoscaling-auth:report [<app>|--global] [--format stdout|json] [--include-metadata] # Displays a scheduler-k3s autoscaling auth report for one or more apps
scheduler-k3s:charts:clear <chart> # Clear all chart-specific helm values for a chart
scheduler-k3s:labels:clear <app|--global> [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Clear all labels for an app or a single process-type/resource-type scope
scheduler-k3s:labels:set <app|--global> <property> (<value>) [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE> # Set or clear a label for a given app/process-type/resource-type combination
scheduler-k3s:labels:set --replace <app|--global> <key=value> [<key=value> ...] [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE> # Replace the entire label map for a given app/process-type/resource-type combination
scheduler-k3s:labels:report [<app>|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Displays a scheduler-k3s labels report for one or more apps
scheduler-k3s:node-sysctls:clear [--global|--profile PROFILE] # Clear all node-level kernel sysctls for unprofiled nodes or a single node profile
scheduler-k3s:node-sysctls:set <sysctl> (<value>) [--global|--profile PROFILE] # Set or clear a node-level kernel sysctl for unprofiled nodes or a single node profile
scheduler-k3s:node-sysctls:report [--format stdout|json] # Displays the node-level kernel sysctls applied to each scope
scheduler-k3s:node-sysctls:set --replace <sysctl=value> [<sysctl=value> ...] [--global|--profile PROFILE] # Replace the entire node-level kernel sysctl map for unprofiled nodes or a single node profile
scheduler-k3s:node-sysctls:report [--format stdout|json] [--stored] [--global|--profile PROFILE] # Displays the node-level kernel sysctls for each scope
scheduler-k3s:preview <app> [--context N] [--show-secrets] [--show-secrets-decoded] # Displays a diff between the current and next deployment for an app
scheduler-k3s:profiles:add <profile> [--role ROLE] [--insecure-allow-unknown-hosts] [--taint-scheduling] [--kubelet-args KUBELET_ARGS] # Adds a node profile to the k3s cluster
scheduler-k3s:profiles:list [--format json|stdout] # Lists all node profiles in the k3s cluster
Profile names must be alphanumeric, may include internal dashes, cannot start/end with a dash, and must be ≤32 characters. Other than the `--server-ip` flag, all flags used for `scheduler-k3s:cluster:add` are valid for the `scheduler-k3s:profiles:add` command.
Profile names must be lowercase alphanumeric, may include internal dashes, cannot start/end with a dash, and must be ≤26 characters. The limit is not arbitrary: the name becomes part of the helm release backing that profile's [non-namespaced sysctls](#non-namespaced-sysctls), and helm caps a release name at 53 characters. Other than the `--server-ip` flag, all flags used for `scheduler-k3s:cluster:add` are valid for the `scheduler-k3s:profiles:add` command.
#### scheduler-k3s:profiles:remove
@@ -595,6 +604,49 @@ dokku scheduler-k3s:annotations:set node-js-app annotation.key --resource-type d
A `ps:restart` is required after removing annotations in order to remove them from running resources.
#### Replacing the entire annotation set
The per-key form above changes one annotation at a time, so matching the stored annotations to a declared set means computing the difference and issuing one command per added, changed and removed key. The `--replace` flag writes the whole map for a scope in a single call, taking `key=value` pairs instead of a single key and value.
Anything previously stored for that scope and not named in the call is removed. `--resource-type` is still required, and `--process-type` scopes the replacement the same way it scopes a single key.
```shell
dokku scheduler-k3s:annotations:set --replace node-js-app --resource-type deployment --process-type web annotation.key=annotation.value
```
Every pair is parsed before anything is written, so a rejected pair leaves the stored annotations untouched. A pair without a `=`, a pair with an empty key, and a key named more than once are all rejected.
A pair ending in `=` stores an empty value, which the per-key form cannot express - there, an empty value deletes the key instead.
> An empty pair list is rejected rather than treated as a request to remove everything, so a generated list that expands to nothing cannot silently drop an app's annotations. Use `scheduler-k3s:annotations:clear` for that.
#### Clearing annotations
The `scheduler-k3s:annotations:clear` command removes every annotation configured for an app.
```shell
dokku scheduler-k3s:annotations:clear node-js-app
```
The `--process-type` and `--resource-type` flags narrow what is cleared, and filter the same way they do for `scheduler-k3s:annotations:report`. Omitting both clears every scope.
A `ps:restart` is required after clearing annotations in order to remove them from running resources.
#### Displaying annotations
Configured annotations can be inspected with the `scheduler-k3s:annotations:report` command. Without arguments, it iterates every app and prints all annotations. Passing an app name (or `--global`) scopes the report:
@@ -658,15 +710,38 @@ A `ps:restart` is required after setting labels in order to have them apply to r
dokku scheduler-k3s:labels:set node-js-app label.key --resource-type deployment --process-type web
```
A `ps:restart` is required after removing labels in order to remove them from running resources.
#### Replacing the entire label set
As with annotations, the `--replace` flag writes the whole map for a scope in a single call, taking `key=value` pairs instead of a single key and value.
Anything previously stored for that scope and not named in the call is removed. `--resource-type` is still required, `--process-type` scopes the replacement, every pair is parsed before anything is written, and a pair ending in `=` stores an empty value. An empty pair list is rejected; use `scheduler-k3s:labels:clear` to remove everything.
#### Clearing labels
The `scheduler-k3s:labels:clear` command removes every label configured for an app, with the same optional `--process-type` and `--resource-type` filters as `scheduler-k3s:labels:report`.
A `ps:restart` is required after clearing labels in order to remove them from running resources.
#### Displaying labels
Configured labels can be inspected with the `scheduler-k3s:labels:report` command. The surface mirrors `scheduler-k3s:annotations:report`:
@@ -809,9 +884,19 @@ After execution, Dokku will include the following resources for each specified t
If the `--global` flag is specified instead of an app name, a custom helm chart is created on the fly with the above resources.
##### Replacing Authentication Metadata
Without `--replace`, the metadata supplied is merged into whatever is already stored for the trigger, so a key that is no longer named stays behind. The `--replace` flag drops the trigger's existing metadata before writing, making the stored set exactly what was specified.
In the example above, a `datadogSite` key configured earlier is removed rather than left in place. At least one `--metadata` flag is required with `--replace`; omit the flag entirely to remove everything, as below.
##### Removing Authentication Resources
To remove a configured authenticatin resource, run the `scheduler-k3s:autoscaling-auth:set` command with no metadata specified. Subsequent deploys will not include these resources.
To remove a configured authentication resource, run the `scheduler-k3s:autoscaling-auth:set` command with no metadata specified. Subsequent deploys will not include these resources.
Passing a non-namespaced sysctl this way fails the deploy rather than silently dropping the value, since it provably cannot take effect within a pod. Note this differs from the `docker-local` scheduler, where such an option is passed straight through to `docker run`.
Kubernetes further splits namespaced sysctls into a *safe* list that any pod may set, and everything else. A sysctl outside the safe list - `net.core.somaxconn`, for example - is rejected at pod admission unless the node's kubelet was started with a matching `allowed-unsafe-sysctls` value, which can be supplied at cluster initialization or when joining a node.
Kubernetes further splits namespaced sysctls into a _safe_ list that any pod may set, and everything else. A sysctl outside the safe list - `net.core.somaxconn`, for example - is rejected at pod admission unless the node's kubelet was started with a matching `allowed-unsafe-sysctls` value, which can be supplied at cluster initialization or when joining a node.
A profile scope inherits everything set globally and overrides it on conflict, so each node is covered by exactly one DaemonSet and no two ever write the same value. Note that the server node created by `scheduler-k3s:initialize` never carries a profile label, so only globally-scoped sysctls reach it.
Use `node-sysctls:report` to see the resolved set for every scope.
A profile created by an older Dokku may carry a name outside the [current rules](#adding-profiles), such as one containing uppercase or longer than 26 characters. Dokku cannot name a helm release after such a profile, so it is skipped with a warning and `node-sysctls:set --profile` refuses it. Recreate the profile under a valid name to give it sysctls; `scheduler-k3s:profiles:remove` still removes the old one.
The `--replace` flag writes the whole set for a scope in one call, taking `sysctl=value` pairs instead of a single name and value. Anything previously set for that scope and not named in the call is cleared, with the same caveat about the value staying on affected nodes until they reboot.
Every pair is parsed before anything is written, so a rejected pair leaves the stored sysctls untouched. A pair without a `=`, a pair with an empty key, and a key named more than once are all rejected. An empty pair list is rejected rather than treated as a request to remove everything.
To remove every sysctl for a scope, use `scheduler-k3s:node-sysctls:clear`.
Clearing a scope removes its DaemonSet. As with clearing a single sysctl, the values it last wrote stay in place on affected nodes until they reboot.
Use `node-sysctls:report` to see the resolved set every scope's DaemonSet applies. A profile's entry therefore includes everything it inherits from the global scope.
Because that report resolves each scope, a profile's entry does not distinguish a sysctl the profile sets itself from one it inherits. To report only the map each scope stores - the map its own `node-sysctls:set` and `node-sysctls:clear` calls write - add the `--stored` flag:
```shell
dokku scheduler-k3s:node-sysctls:report --stored
```
```
scope sysctl value
--global vm.overcommit_memory 1
--global vm.swappiness 20
edge-workers vm.swappiness 60
```
The global scope stores exactly what it applies, so `--stored` reports the same thing for `--global` either way.
A configuration tool holding a declared map for a scope should compare it against `--stored`. Comparing against the resolved set never converges a profile, since a profile that stores nothing of its own still reports every globally-set sysctl.
The report covers every scope unless `--global` or `--profile` narrows it to one, the same way those flags scope `node-sysctls:set` and `node-sysctls:clear`. Only one of the two may be given.
The DaemonSet pulls `busybox` and `registry.k8s.io/pause` by default. On an air-gapped cluster or one behind a registry mirror, point them elsewhere:
```shell
@@ -953,7 +1092,7 @@ See the [Kustomize](https://kustomize.io/) website for more details on how to us
### Using kubectl remotely
> [!WARNING]
> Certain ports must be open for interacting with the remote kubernets api. Refer to the [K3s networking documentation](https://docs.k3s.io/installation/requirements?os=debian#networking) for the required open ports between servers prior to running the command.
> Certain ports must be open for interacting with the remote kubernetes api. Refer to the [K3s networking documentation](https://docs.k3s.io/installation/requirements?os=debian#networking) for the required open ports between servers prior to running the command.
By default, Dokku assumes that all it controls all actions on the cluster, and thus does not expose the `kubectl` binary for administrators. To interact with kubectl, you will need to retrieve the `kubeconfig` for the cluster and configure your client to use that configuration.
@@ -998,18 +1137,20 @@ The default value for the `kube-context` is an empty string, and will result in
Dokku includes a number of helm charts by default with settings that are optimized for Dokku. That said, it may be useful to further customize the charts for a given environment. Chart overrides are managed via the `scheduler-k3s:charts:set` command, which takes a `<chart-name>.<property>` argument and a value. Chart overrides are always global because helm charts are not managed on a per-app basis.
> Properties follow dot-notation, and are expanded according to Helm's internal logic. See the [Helm documentation](https://helm.sh/docs/helm/helm_install/#helm-install) for `helm install` for further details.
Chart overrides are values passed to a chart, not the version of the chart itself. Dokku pins the version of every chart it manages, and those versions are not user-configurable. The `scheduler-k3s:ensure-charts` command reconciles each installed release back to the pinned version, upgrading or downgrading a release that has drifted away from it.
Property names may contain `/` (e.g. for Kubernetes-style annotation keys such as `service.annotations.prometheus.io/scrape`) and values may span multiple lines; both are preserved verbatim.
To unset a chart property, omit the value from the `scheduler-k3s:charts:set` call:
Configured chart overrides can be inspected with the `scheduler-k3s:charts:report` command. Without an argument, it lists every chart known to Dokku along with any configured overrides; passing a chart name scopes the report to that chart:
@@ -1035,6 +1176,37 @@ Alternatively, a comma separated list of chart names can be specified to only fo
The per-property form above changes one chart override at a time, so matching the stored overrides for a chart to a declared set means computing the difference and issuing one command per added, changed and removed property. The `--replace` flag writes the whole override map for a chart in a single call, taking the chart name followed by `property=value` pairs instead of a single `<chart-name>.<property>` argument and value.
Anything previously stored for that chart and not named in the call is removed. Overrides for other charts are left untouched.
Every pair is parsed before anything is written, so a rejected pair leaves the stored overrides untouched. A pair without a `=`, a pair with an empty property, and a property named more than once are all rejected. Only the first `=` in a pair separates the property from the value, so values may themselves contain `=`.
A pair ending in `=` stores an empty value, which the per-property form cannot express - there, an empty value deletes the property instead.
> An empty pair list is rejected rather than treated as a request to remove everything, so a generated list that expands to nothing cannot silently drop a chart's overrides. Use `scheduler-k3s:charts:clear` for that.
#### Clearing chart overrides
The `scheduler-k3s:charts:clear` command removes every override configured for a chart.
```shell
dokku scheduler-k3s:charts:clear cert-manager
```
As with any other chart property change, a `scheduler-k3s:ensure-charts --force` or `scheduler-k3s:ensure-charts --charts <chart>` call is required after replacing or clearing chart overrides in order to have them apply.
### Persistent storage
K3s apps mount persistent volumes through the Dokku storage plugin. A `storage:create --scheduler k3s` call provisions a PersistentVolumeClaim through a small per-entry helm release; `storage:mount <app> <name> --container-dir <path>` then attaches it to an app. Because storage entries are global, the same PVC can be mounted into multiple apps (for `ReadWriteMany` workloads) or into the same app at multiple subpaths.
@@ -1054,6 +1226,8 @@ git push dokku master
For a hostPath-backed PV (no StorageClass), pass `<path>` as the second positional argument and omit `--storage-class-name`. The plugin renders both the PV and the PVC into the entry's helm release. The `--reclaim-policy` flag (`Retain` or `Delete`) controls whether the underlying PV survives `storage:destroy`. Annotations and labels set via `storage:annotations:set` and `storage:labels:set` propagate to both the PVC and the PV so backup tools (Velero, Longhorn snapshots) can find them.
`storage:mount --process-type <proc>` scopes a volume to a single process type, so only that deployment's pods mount the PVC; a mount left in the default `_default_` scope is mounted by every deployment. Cron jobs are keyed by a generated cron ID rather than a Procfile process type, so they mount default-scoped volumes only. Only deploy-phase attachments are mounted on k3s - `--phase run` has no effect here.
The legacy `storage:mount <app> <host>:<container>` colon form is rejected on k3s apps; create a named entry instead. See [Persistent Storage](/docs/advanced-usage/persistent-storage.md) for the full command reference.
### Chart upgrade callbacks
@@ -1152,7 +1326,7 @@ Two differences are worth noting:
The `k3s` scheduler supports a minimal list of resource _limits_ and _reservations_:
- cpu: is specified in number of CPUs a process can access.
- memory: should be specified with a suffix of `b` (bytes), `Ki` (kilobytes), `Mi` (megabytes), `Gi` (gigabytes). Default unit is `Mi` (megabytes).
- memory: should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes), `t` (terabytes) or `p` (petabytes). Suffixes are case-insensitive and may be followed by `i` and/or `b`, such as `512Mi`, `1G`, or `1GiB`. Units are binary, so `1G` and `1Gi` are equivalent. Default unit is `Mi` (megabytes).
If unspecified for any task, the default reservation will be `.1` CPU and `128Mi` RAM, with no limit set for either CPU or RAM. This is to avoid issues with overscheduling pods on a cluster. To avoid issues, set more specific values for at least resource reservations. If unbounded utilization is desired, set CPU and Memory to `0m` and `0Mi`, respectively.
@@ -1167,7 +1341,7 @@ If unspecified for any task, the default reservation will be `.1` CPU and `128Mi
> The `Report flags` column lists the CLI argument names accepted by `scheduler-k3s:report`. The JSON keys emitted by `scheduler-k3s:report --format json` are the same names with the leading `--scheduler-k3s-` stripped (e.g. `deploy-timeout`, `global-deploy-timeout`, `computed-deploy-timeout`). Legacy keys with the `scheduler-k3s-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `cert-issuer-kind` | app + global | `ClusterIssuer` | `--scheduler-k3s-cert-issuer-kind`, `--scheduler-k3s-global-cert-issuer-kind`, `--scheduler-k3s-computed-cert-issuer-kind` | Kind of the manually managed cert-manager issuer referenced by `cert-issuer-name`, either `Issuer` or `ClusterIssuer` |
| `cert-issuer-name` | app + global | none | `--scheduler-k3s-cert-issuer-name`, `--scheduler-k3s-global-cert-issuer-name`, `--scheduler-k3s-computed-cert-issuer-name` | Name of a manually managed cert-manager issuer to request certificates from, taking precedence over the letsencrypt integration. Set to `false` to opt an app out of a global value |
| `deploy-timeout` | app + global | `300s` | `--scheduler-k3s-deploy-timeout`, `--scheduler-k3s-global-deploy-timeout`, `--scheduler-k3s-computed-deploy-timeout` | Timeout for a single helm install/upgrade cycle |
@@ -1181,8 +1355,8 @@ If unspecified for any task, the default reservation will be `.1` CPU and `128Mi
| `letsencrypt-server` | app + global | `prod` | `--scheduler-k3s-letsencrypt-server`, `--scheduler-k3s-global-letsencrypt-server`, `--scheduler-k3s-computed-letsencrypt-server` | ACME directory (`prod` or `staging`) used for app certificates, or `false` to disable all automatic certificate issuance |
| `namespace` | app + global | `default` | `--scheduler-k3s-namespace`, `--scheduler-k3s-global-namespace`, `--scheduler-k3s-computed-namespace` | Kubernetes namespace into which the app's resources are installed |
| `network-interface` | global only | `eth0` | `--scheduler-k3s-global-network-interface`, `--scheduler-k3s-computed-network-interface` | Host network interface used by k3s |
| `node-sysctls-image` | global only | `busybox:1.36` | `--scheduler-k3s-global-node-sysctls-image` | Image used to apply node-level sysctls, override for air-gapped clusters |
| `node-sysctls-pause-image` | global only | `registry.k8s.io/pause:3.9` | `--scheduler-k3s-global-node-sysctls-pause-image` | Image keeping the node sysctls daemonset pods running |
| `node-sysctls-image` | global only | `busybox:1.36` | `--scheduler-k3s-global-node-sysctls-image`, `--scheduler-k3s-computed-node-sysctls-image` | Image used to apply node-level sysctls, override for air-gapped clusters |
| `node-sysctls-pause-image` | global only | `registry.k8s.io/pause:3.9` | `--scheduler-k3s-global-node-sysctls-pause-image`, `--scheduler-k3s-computed-node-sysctls-pause-image` | Image keeping the node sysctls daemonset pods running |
| `rollback-on-failure` | app + global | `false` | `--scheduler-k3s-rollback-on-failure`, `--scheduler-k3s-global-rollback-on-failure`, `--scheduler-k3s-computed-rollback-on-failure` | When `true`, helm rolls back the release if a deploy fails |
| `token` | global only | none | `--scheduler-k3s-global-token` (masked as `*******` in default stdout output; the raw value is returned when queried via `--format json` or when this flag is requested explicitly) | Cluster join token used by `scheduler-k3s:cluster-add` |
The `null` scheduler does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ip:port-combination) for more information on the aforementioned use case.
The `null` scheduler does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ipport-combination) for more information on the aforementioned use case.
@@ -156,6 +156,6 @@ Deployment tasks are currently executed directly on the primary Dokku server.
> The `Report flags` column lists the CLI argument names accepted by `scheduler:report`. The JSON keys emitted by `scheduler:report --format json` are the same names with the leading `--scheduler-` stripped (e.g. `selected`, `global-selected`, `computed-selected`). Legacy keys with the `scheduler-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `selected` | app + global | `docker-local` | `--scheduler-selected`, `--scheduler-global-selected`, `--scheduler-computed-selected` | Scheduler plugin used to deploy this app (`docker-local`, `k3s`, etc.) |
| `shell` | app + global | none | `--scheduler-shell`, `--scheduler-global-shell`, `--scheduler-computed-shell` | Shell used by `enter`/`run` commands when entering a container |
@@ -180,6 +180,18 @@ A healthcheck entry takes the following properties:
See the [docker-container-healthchecker](https://github.com/dokku/docker-container-healthchecker) documentation for more details on how healthchecks are interpreted.
### Healthchecks and label-based proxies
When using the `docker-local` scheduler, the `nginx` proxy only routes traffic to new containers once healthchecks have passed. The `caddy`, `haproxy`, `openresty`, and `traefik` proxies instead discover containers via docker labels, which are attached when a container is created, and will route requests to a new container as soon as it is running. As Dokku's healthchecks are run from the host, these proxies are not aware of their result.
To reduce the number of requests routed to a container that is not yet ready, the first `readiness` healthcheck with a `path` for the `web` process is translated into proxy-specific configuration:
- [Caddy](/docs/networking/proxies/caddy.md#healthchecks): Active healthchecks against the `path`, and retries of failed requests against other containers.
- [OpenResty](/docs/networking/proxies/openresty.md#healthchecks): Active healthchecks against the `path`, and retries of failed requests against other containers.
- [Traefik](/docs/networking/proxies/traefik.md#healthchecks): Active healthchecks against the `path`.
Haproxy does not support this configuration. Healthchecks of other types are not translated, as proxy healthchecks run for the lifetime of a container. Translating a `startup` healthcheck would turn it into a `readiness` healthcheck.
## Manually invoking checks
Checks can also be manually invoked via the `checks:run` command. This can be used to check the status of an application via cron to provide integration with external healthchecking software.
Property writes for a given plugin and app are serialized by a lock on that app's property directory, and each write replaces the file atomically, so readers never see a partially written value. The list and map helpers, such as `PropertyListAdd` and `PropertyMapSet`, hold the lock while they read, change, and write a property.
Code that reads a property and writes a value derived from it must hold the lock for the whole operation, or concurrent commands for the same app will overwrite each other's changes:
Inside a `PropertyLock` callback, writes must go through the `LockedProperties` handle, as the public writers take the same lock. Shell callbacks may call the regular `fn-plugin-property-*` helpers.
### Per-App Data
Application-specific data sometimes lives in `$DOKKU_ROOT/<app>/`:
@@ -299,7 +333,7 @@ Global settings can be configured via:
## Key Design Decisions
| Decision | Rationale |
|----------|-----------|
|---------- | -----------|
| **Plugin-based architecture** | Enables extensibility without modifying core code. Community plugins can add databases, caching, and other services. |
| **Bash + Go hybrid** | Bash for orchestration and simple commands; Go for performance-critical operations and complex logic. |
| **Trigger system** | Loose coupling between plugins. Plugins don't need to know about each other; they just fire and respond to events. |
- Description: Sets one or more config values for an app without restarting (when --no-restart flag is specified)
- Description: Sets one or more config values for an app without restarting (when --no-restart flag is specified). Exits non-zero without restarting the app when the config cannot be persisted.
> To avoid issues with community plugins, this plugin trigger should be used _only_ for core plugins. Please avoid using this trigger in your own plugins.
@@ -673,8 +688,10 @@ APP="$1"; PROPERTY="$2"
### `cron-entries`
- Description: Allows injecting cron tasks into the written out scheduled cron task list. Each entry is newline delimited, and individual tasks come in the form `$SCHEDULE;$FULL_COMMAND;$ARBITRARY_DATA`. Individual implementations of cron writing can decide whether and how to include these cron tasks. The `ARBITRARY_DATA` includes the log file path for the basic `docker-local` cron implementation.
- Invoked by:
- Arguments: `$DOKKU_SCHEDULER`
When `$CRON_ENTRY_FORMAT` is `json`, each entry may instead be printed as a json object on its own line, with the keys `schedule`, `command`, `log-file` and `mailto`. Only `schedule` and `command` are required. A task with a `mailto` value is written to the crontab after a `MAILTO` line of its own and followed by one resetting `MAILTO` to the global value, or to the `dokku` user when no global value is set, so the value only applies to that task. Its output is mailed to that address in addition to being appended to its `log-file`. Json entries and `;` delimited entries may be printed together. Older versions of Dokku do not pass `$CRON_ENTRY_FORMAT`, and fail to write the crontab if a json entry is printed, so a json entry should only be printed when the argument is `json`.
- Invoked by: `dokku cron:list --global`, and when the `dokku` user crontab is written
- Description: emits docker arguments scoped to a specific Procfile process type. The `docker-options` plugin implements this trigger to surface options registered via `docker-options:add --process <PROC>`. `$PROC_TYPE` may be empty (or set to the magic `_default_` value) to signify default-scope docker deploy options.
- Description: emits docker arguments scoped to a specific Procfile process type. The `docker-options` plugin implements this trigger to surface options registered via `docker-options:add --process <PROC>`, and the `storage` plugin implements it to emit the `-v` flags for mounts made with `storage:mount --process-type <PROC>`. `$PROC_TYPE` may be empty (or set to the magic `_default_` value) to signify default-scope docker deploy options. Note that the two plugins split the work differently: `docker-options` emits only its process-specific scope here and its default scope through `docker-args-deploy`, while `storage` emits both scopes here and does not implement `docker-args-deploy` at all. A handler that exits non-zero aborts the deploy, including the ephemeral containers that run `app.json` deploy tasks, so a handler that has nothing to contribute for an app should emit nothing and exit zero rather than fail.
@@ -1331,6 +1353,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
- Invoked by: `nginx-vhosts#build-config`
- Arguments: `$APP $TEMPLATE_TYPE`
- The `TEMPLATE_TYPE` argument can be one of: `[app-config, hsts-config, validate-config]`
- The `validate-config` template can make use of the following variables: `$.NGINX_CONF $.NGINX_USER`
- Example:
```shell
@@ -2349,6 +2372,24 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `proxy-routes-list`
- Description: Emits one `process|port|path|strip` line per path-based route declared via `proxy:route:set`, sorted by descending path length. Backends use this to render path-based routes in their config or container labels.
@@ -2396,7 +2437,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
### `ps-set-scale`
- Description: Sets the scale for an app based on a specified formation (process-type=quantity). Any unspecified process types will be left as is.
- Description: Sets the scale for an app based on a specified formation (process-type=quantity). Any unspecified process types will be left as is, or have their process count set to zero when `$CLEAR_EXISTING` is `true`. Each process tuple must name a process type, and process counts must be zero or greater.
@@ -13,7 +13,7 @@ All pull requests have tests run against them on [GitHub Actions](https://github
If you wish to skip tests for a particular commit, e.g. documentation changes, you may add the `[ci skip]` designator to your commit message. Commits that _should_ be tested but have the above designator will not be merged.
While we do provide official packages for a variety of platforms, as our test suite currently runs on Ubuntu Noble 24.04, we only provide official installation support for that platform and the supported LTS releases of Ubuntu (currently 22.04, and 24.04).
While we do provide official packages for a variety of platforms, as our test suite currently runs on Ubuntu Noble 24.04, we only provide official installation support for that platform and the supported LTS releases of Ubuntu (currently 22.04, 24.04, and 26.04).
The `Makefile` installs dependencies and builds plugins for the architecture of the host it runs on. The `TARGETARCH` environment variable overrides the architecture used for both the downloaded dependencies and the plugin builds, while the `GOARCH` environment variable overrides the architecture used for the plugin builds only. Example:
```shell
sudo TARGETARCH=arm64 make install
```
The `Makefile` allows source URLs to be overridden to include customizations from your own repositories. The `DOCKER_URL`, `PLUGN_URL`, `SSHCOMMAND_URL` and `STACK_URL` environment variables may be set to override the defaults (see the `Makefile` for how these apply). Example:
When run from a terminal, the bootstrap script shows each install step with a spinner and the last few lines of output from the running step. When the output is not a terminal - such as in CI, Vagrant, or when piping to a file - it prints one line when each step starts and one when it finishes.
All command output is written to a log file at `/var/log/dokku/install-<timestamp>.log`, with a timestamp on every line. If a step fails, the full output of the failed step is printed along with the path to the full log.
The following environment variables change the output:
-`DOKKU_VERBOSE=1`: Stream the full output of every command instead of the condensed view.
-`TRACE=1`: Same as `DOKKU_VERBOSE=1`, and also enables `set -x`.
## Automatic configuration by the bootstrap script
After installing, the bootstrap script fills in configuration that is still missing:
- SSH keys: If no SSH keys have been added to Dokku, `DOKKU_SKIP_KEY_FILE` is not `true`, and `/root/.ssh/authorized_keys` contains keys, each key is added via `dokku ssh-keys:add`. The first key is named `admin`, and any others are named `admin-2`, `admin-3`, and so on.
- Global domain: If no global domain is set, it is set to the server's primary IPv4 address via `dokku domains:set-global`. On a server behind NAT, this is the private address.
Anything already configured - for example via the `DOKKU_KEY_FILE` or `DOKKU_HOSTNAME` debconf settings - is left as is. The summary at the end of the install shows what was configured, along with the commands to configure anything that was not.
## Custom Herokuish build
Dokku ships with a pre-built version of the [Herokuish](https://github.com/gliderlabs/herokuish) component by default. If you want to build your own version you can specify that with an environment variable.
@@ -37,4 +37,14 @@ After setting the desired options, proceed with the installation as described ab
| dokku/hostname | string | dokku.me | Hostname, used as vhost domain and for showing app URL after deploy |
| dokku/skip_key_file| boolean | false | Don't check for the existence of the dokku/key_file. Warning: Setting this to true, will require you to manually add an SSH key later on. |
| dokku/key_file | string | /root/.ssh/id_rsa.pub | Path on disk to an SSH key to add to the Dokku user (Will be ignored on `dpkg-reconfigure`) |
| dokku/nginx_enable | boolean | true | Enable nginx-vhosts plugin. When `false`, the plugin is installed but left disabled, and the `nginx:*` commands are unavailable. |
| dokku/install_default_site | boolean | true | Install a catch-all nginx default site on fresh install |
### Enabling the nginx-vhosts plugin after installation
If Dokku was installed with `dokku/nginx_enable` set to `false`, the nginx-vhosts plugin can be enabled later. The `plugin:install --core` call runs the plugin's install trigger, which sets up the nginx configuration required by Dokku.
@@ -27,7 +27,7 @@ Alternatively, you can use `docker-compose.yml`:
```yaml
services:
dokku:
image:dokku/dokku:0.38.27
image:dokku/dokku:0.38.34
container_name:dokku
network_mode:bridge
ports:
@@ -67,7 +67,7 @@ Compose dependents can gate on the healthcheck via `depends_on` with `condition:
```yaml
services:
dokku:
image:dokku/dokku:0.38.27
image:dokku/dokku:0.38.34
# ...
bootstrap:
image:alpine:3.20
@@ -87,8 +87,10 @@ Dokku is run in the following configuration:
- Data within the container is stored on the host within the `/var/lib/dokku` directory.
- Image build cache is set to the data dir + `/home/dokku`.
- The docker socket is mounted into container.
-`DOKKU_HOST_ROOT` and `DOKKU_LIB_HOST_ROOT` tell Dokku how the host Docker daemon sees those same trees when creating bind mounts (app repos and plugin data such as named storage).
-`coredns-docker` (a CoreDNS plugin that serves DNS records for sibling Docker containers) is installed transitively via the dokku deb's `Depends:`, runs under runit on `127.0.0.1:1053` inside the container, and is what the `nginx-vhosts``dns-resolver` property can point at to enable `<app>.<process-type>.docker` upstream resolution - see the [nginx proxy doc](/docs/networking/proxies/nginx.md#upstream-resolution-via-coredns-docker) for the operator-facing behavior.
Application repositories, plugin config, as well as plugin data are persisted to disk within the specified host directory for `/var/lib/dokku`.
Application repositories, plugin config, as well as plugin data are persisted to disk within the specified host directory for `/var/lib/dokku`.`DOKKU_LIB_HOST_ROOT` must point at that host path's `var/lib/dokku` subtree so Docker `-v` mounts resolve under the data volume rather than under the host's own `/var/lib/dokku`.
Other docker container options can also be used when running Dokku, though the specific outcome will depend upon the specified options. For example, the Dokku container's nginx port can be bound to a specific host ip by specifying `--publish $HOST_IP:8080:80`, where `$HOST_IP` is the IP desired. Please see the [docker container run documentation](https://docs.docker.com/engine/reference/commandline/run/) for further explanation for various docker arguments.
The alternative is to build a custom docker image via a custom Dockerfile. This Dockerfile can run any `plugin:install` command, though the `install` trigger must be skipped via the `--skip-install-trigger`. The version installed at that time will be the one that persists. Below is an example Dockerfile showing this method.
```Dockerfile
FROMdokku/dokku:0.38.27
FROMdokku/dokku:0.38.34
RUN dokku plugin:install https://github.com/dokku/dokku-postgres.git --skip-install-trigger
RUN dokku plugin:install https://github.com/dokku/dokku-redis.git --skip-install-trigger
@@ -8,7 +8,7 @@ Dokku is an extensible, open source Platform as a Service that runs on a single
To start using Dokku, you'll need a system that meets the following minimum requirements:
- Operating Systems: [Ubuntu 22.04/24.04](https://www.ubuntu.com/download) or [Debian 11+ x64](https://www.debian.org/distrib/)
- Operating Systems: [Ubuntu 22.04/24.04/26.04](https://www.ubuntu.com/download) or [Debian 11+ x64](https://www.debian.org/distrib/)
- Supported Architectures: `AMD64` (`x86_64`) and `arm64` (`armv8`)
- Minimum Memory:
- Docker Scheduler: 1GB of system memory, or [add swap memory](/docs/getting-started/advanced-installation.md#vms-with-less-than-1-gb-of-memory)
@@ -23,7 +23,7 @@ Please see the [dns documentation](/docs/networking/dns.md) and [domains documen
## Installing the latest stable version
This is the simple method of installing Dokku. For alternative methods of installation, see the [advanced install guide](/docs/getting-started/advanced-installation.md#configuring).
This is the simple method of installing Dokku. For alternative methods of installation, see the [advanced install guide](/docs/getting-started/advanced-installation.md#installing-via-other-methods).
### 1. Install Dokku
@@ -31,15 +31,17 @@ To install the latest stable version of Dokku, you can run the following shell c
The installation process takes about 5-10 minutes, depending upon internet connection speed.
### 2. Setup SSH key and Virtualhost Settings
Once the installation is complete, you should configure an ssh key and set your global domain.
The bootstrap script adds the keys in root's `~/.ssh/authorized_keys` and sets the server's IP as the global domain when those are not already configured. The summary at the end of the install shows what was set. See the [advanced installation documentation](/docs/getting-started/advanced-installation.md#automatic-configuration-by-the-bootstrap-script) for details.
Otherwise, once the installation is complete, you should configure an ssh key and set your global domain.
```shell
# usually your key is already available under the current user's `~/.ssh/authorized_keys` file
### I can't connect to my application because the server is sending an invalid response, or can't provide a secure connection
This is normally a config problem with your app, rather than an issue with Dokku.
This is normally a config problem with your app, rather than an issue with Dokku.
One common cause is an application configured to enforce secure connections/HSTS, but SSL is not set up. In Ruby on Rails, if your `application.rb` or `config/environments/production.rb` include the line `configure.force_ssl = true`, try commenting that line out and redeploying. If this solves the issue temporarily, longer-term you should consider [configuring SSL](/docs/configuration/ssl.md).
### I deployed a new application, but an existing application is being served when I visit it
This problem has the same cause as the previous issue: your application is attempting to enforce SSL, but SSL is not configured. Disabling SSL enforcement should solve the problem.
The underlying cause is best understood with an example. Let's say you already have `https://apples.example.com/` working, and you're trying to deploy `bananas.example.com/`. But whenever you visit `bananas.example.com` you see `apples.example.com` instead. This is caused by `bananas.example.com` forcing SSL:
The underlying cause is best understood with an example. Let's say you already have `https://apples.example.com/` working, and you're trying to deploy `bananas.example.com/`. But whenever you visit `bananas.example.com` you see `apples.example.com` instead. This is caused by `bananas.example.com` forcing SSL:
1. Your browser sends a request to `http://bananas.example.com`.
1.`bananas.example.com` issues a redirect to `https://bananas.example.com`.
1. Your browser sends a request to `https://bananas.example.com`. But you haven't configured SSL for `bananas.example.com` yet! So this request ends up routed to `https://apples.example.com`, which _does_ have an SSL config.
### My application deploys properly, but won't load in browser "connection refused"
This could be a result of a bad proxy configuration (`http:5000:5000` may be incorrect). Run `dokku ports:report node-js-app` to check if your app has the correct proxy configuration. It should show something like the following.
The Official Dokku website is always a great place to visit. It features links to oft-used developer tools, community plugins, and guides on using Dokku.
> The `Report flags` column lists the CLI argument names accepted by `network:report`. The JSON keys emitted by `network:report --format json` are the same names with the leading `--network-` stripped (e.g. `attach-post-create`, `global-attach-post-create`, `computed-attach-post-create`). Legacy keys with the `network-` prefix (e.g. `network-attach-post-create`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `attach-post-create` | app + global | none | `--network-attach-post-create`, `--network-global-attach-post-create`, `--network-computed-attach-post-create` | Networks attached to a container immediately after creation, before the deploy phase |
| `attach-post-deploy` | app + global | none | `--network-attach-post-deploy`, `--network-global-attach-post-deploy`, `--network-computed-attach-post-deploy` | Networks attached to a container after it passes healthchecks |
| `bind-all-interfaces` | app + global | `false` | `--network-bind-all-interfaces`, `--network-global-bind-all-interfaces`, `--network-computed-bind-all-interfaces` | When `true`, binds containers to `0.0.0.0` instead of the default Docker network address |
@@ -32,7 +32,55 @@ The Caddy plugin has specific rules for routing requests:
- If no `http:80` mapping is found, the first `http` port mapping is used for http requests.
- If no `https:443` mapping is found, the first `https` port mapping is used for https requests.
- If no `https` mapping is found, the container port from `http:80` will be used for https requests.
- Requests are routed as soon as the container is running and passing healthchecks.
- Requests are routed as soon as the container is running, before Dokku's healthchecks have passed. See the [healthchecks](#healthchecks) section for how Caddy avoids routing to containers that are not yet ready.
### Healthchecks
When an app has a readiness healthcheck defined in its `app.json` file with a `path` property, Dokku automatically generates Caddy healthcheck and retry labels. These configure Caddy to periodically check each container, stop routing traffic to containers that fail the check, and retry requests that fail to connect against another container.
The following `app.json` healthcheck properties are mapped to Caddy labels:
| app.json Property | Caddy Label | Description |
|-------------------|-------------|-------------|
| `path` | `caddy.reverse_proxy.health_uri` | The HTTP path to check (required) |
| `port` | `caddy.reverse_proxy.health_port` | The port to check |
| `timeout` | `caddy.reverse_proxy.health_timeout` | Timeout in seconds (formatted as `Xs`, defaults to `5s`) |
| `wait` | `caddy.reverse_proxy.health_interval` | Interval between checks in seconds (formatted as `Xs`, defaults to `5s`) |
| `attempts` | `caddy.reverse_proxy.health_fails` | Number of successive failed checks before a container is taken out of rotation (defaults to `3`) |
| `httpHeaders` | `caddy.reverse_proxy.health_headers.<name>` | Headers to send with the check |
Additionally, the following labels are generated:
| Caddy Label | Value | Description |
|-------------|-------|-------------|
| `caddy.reverse_proxy.health_headers.Host` | the first app domain | The `Host` header sent with the healthcheck, matching the header Dokku sends during deploy checks. A `Host` header in `httpHeaders` takes precedence |
| `caddy.reverse_proxy.lb_try_duration` | `5s` | How long a request that fails to connect is retried against other containers |
| `caddy.reverse_proxy.lb_try_interval` | `250ms` | How long to wait between retries |
Example `app.json` configuration:
```json
{
"healthchecks":{
"web":[
{
"name":"web readiness check",
"path":"/health",
"timeout":5,
"type":"readiness",
"wait":10
}
]
}
}
```
Caddy considers a new container healthy until its first check fails. The first check runs as soon as Caddy picks up the new container, and requests that reach the container before then and fail to connect are retried against the still-running old container.
Headers with values containing quotes, backslashes, or control characters are skipped, as are headers whose names contain a `.` or end in an `_<number>` suffix, as these cannot be represented as Caddy labels. The `scheme` property is not mapped, as Caddy uses the same transport for healthchecks as for proxied requests. Any of the generated labels may be overridden via the `caddy:labels:add` command.
> [!NOTE]
> Only the first readiness healthcheck with a `path` property is used. Apps without one are routed as soon as the container is running. See the [zero downtime deploys documentation](/docs/deployment/zero-downtime-deploys.md#healthchecks-and-label-based-proxies) for more information.
### Switching to Caddy
@@ -197,6 +245,8 @@ To switch to Caddy's internal TLS server for certificate provisioning, set the `
dokku caddy:set node-js-app tls-internal true
```
As Caddy only serves https when the `letsencrypt-email` property is set, the internal TLS server is only used for apps when that property is also set. Apps will need to be rebuilt after changing this property.
The default value may also be configured globally with the `--global` flag. Per-app values take precedence over the global value when set.
| `image` | global only | _parsed from `plugins/caddy-vhosts/Dockerfile`_ | `--caddy-global-image`, `--caddy-computed-image` | Docker image used to run the Caddy container |
| `letsencrypt-email` | global only | none | `--caddy-global-letsencrypt-email`, `--caddy-computed-letsencrypt-email` | Contact email enabling letsencrypt; empty disables https issuance |
| `letsencrypt-server` | global only | `https://acme-v02.api.letsencrypt.org/directory` | `--caddy-global-letsencrypt-server`, `--caddy-computed-letsencrypt-server` | ACME directory used when requesting certificates |
@@ -28,7 +28,7 @@ The Haproxy plugin has specific rules for routing requests:
- Haproxy integration is exposed via docker labels attached to containers. Changes in labels require either app deploys or rebuilds.
- While Haproxy will respect labels associated with other containers, only `web` containers have Haproxy labels injected by the plugin.
- Only `http:80` and `https:443` port mappings are supported at this time.
- Requests are routed as soon as the container is running and passing healthchecks.
- Requests are routed as soon as the container is running, before Dokku's healthchecks have passed. Haproxy does not support healthcheck configuration via container labels, so `app.json` healthchecks are not used for routing.
After enabling, the Haproxy container will need to be restarted and apps will need to be rebuilt. All http requests will then be redirected to https.
Certificates are requested over http-01 on port 80, so each domain must resolve to the server. They are kept in `/var/lib/dokku/data/haproxy` and renewed automatically.
#### Customizing the letsencrypt server
The letsencrypt integration is set to the production letsencrypt server by default. To change this, set the `letsencrypt-server` property with the `--global` flag:
| `image` | global only | _parsed from `plugins/haproxy-vhosts/Dockerfile`_ | `--haproxy-global-image`, `--haproxy-computed-image` | Docker image used to run the Haproxy container |
| `letsencrypt-email` | global only | none | `--haproxy-global-letsencrypt-email`, `--haproxy-computed-letsencrypt-email` | Contact email enabling letsencrypt; empty disables https issuance |
| `letsencrypt-server` | global only | `https://acme-v02.api.letsencrypt.org/directory` | `--haproxy-global-letsencrypt-server`, `--haproxy-computed-letsencrypt-server` | ACME directory used when requesting certificates |
@@ -28,6 +28,42 @@ Nginx will proxy the requests in a [round-robin balancing fashion](http://nginx.
> [!NOTE]
> Due to how the plugin is implemented, if an app successfully starts up `web` containers but fails to deploy some other containers, nginx may eventually stop routing requests. Users should revert their code in these cases, or manually trigger `dokku proxy:build-config $APP` in order to ensure requests route to the new web containers.
### Upstream Resolution via coredns-docker
> [!IMPORTANT]
> New as of 0.39.0
Dokku can resolve nginx upstreams through [coredns-docker](https://github.com/dokku/coredns-docker), a CoreDNS plugin that watches the Docker daemon and serves fresh `A` records keyed off container labels. When enabled, the nginx `resolver` directive points at coredns-docker's local socket (`127.0.0.1:1053`) and the rendered upstream is a variable-based `proxy_pass`, which forces nginx to re-resolve at request time honoring DNS TTL. This is how nginx avoids serving the wrong vhost after a host reboot, after `apt upgrade docker`, or after a single container restarts mid-runtime: the on-disk nginx config no longer pins a stale IP. This behavior is opt-in, and nginx uses IP-based `upstream` blocks by default.
`coredns-docker` (>= `0.8.0`) is installed automatically as a transitive `Depends:` of the `dokku` Debian package, so `apt install dokku` and `apt upgrade dokku` pull it in without operator action. The shipped `/etc/coredns/Corefile` already binds `127.0.0.1:1053`, serves the `docker.` zone, and synthesizes `<app>.<process-type>.docker` and `<app>.docker``A` records from Dokku's existing `com.dokku.app-name` and `com.dokku.process-type` container labels. Multiple containers for the same process type collapse onto one multi-A record set, so scaled apps load-balance across instances via DNS without any per-container configuration. The bundled Docker image of Dokku ships coredns-docker preinstalled and runs it under runit on the same port.
No `systemd-resolved` or `/etc/resolv.conf` change is required - nginx talks to `coredns-docker` directly via the `resolver` directive in each app's `nginx.conf`.
To revert to the IP-based upstream block, unset the property and rebuild the config:
```shell
dokku nginx:set --global dns-resolver
dokku proxy:build-config --all
```
The behavior is controlled by three nginx properties, each settable per app or globally:
-`dns-resolver` (default: none) - the resolver address nginx talks to. Resolver-based upstreams are only rendered when this is set.
-`dns-resolver-timeout` (default: `5s`) - how long nginx waits for a DNS response before giving up and returning 502. The default is short so requests fail fast when coredns-docker is unreachable rather than hanging on the nginx built-in default (30s).
-`dns-zone` (default: `docker`) - the suffix appended to `<app>.<process-type>` when constructing the upstream hostname.
> [!NOTE]
> When `dns-resolver` is set, no `upstream` block is rendered for the `web` process, so `proxy-keepalive` has no effect on it. Upstreams for [proxy routes](/docs/networking/proxy-management.md#path-based-routing-to-non-web-processes) to other process types remain IP-based.
See [the `nginx.conf.sigil` reference](/docs/appendices/file-formats/nginx-conf-sigil.md) for the sigil variables that drive the resolver-based render, and how vendored templates can adopt or skip it.
### Nginx Configuration for Undeployed Apps
> [!IMPORTANT]
@@ -123,6 +159,8 @@ App-supplied `nginx.conf.sigil` files are pre-validated automatically at the sta
The wrapper config used for validation does not include the top-level `load_module` directives from the global nginx config, so a `nginx.conf.sigil` that relies on a directive from a dynamically loaded module will fail this validation even when `nginx -t` passes against the real server config. See [Custom nginx modules](/docs/appendices/file-formats/nginx-conf-sigil.md#custom-nginx-modules) for how to supply a custom validation wrapper via the `nginx-app-template-source` trigger's `validate-config` template type.
The wrapper does declare the same `user` directive as the host's main nginx config, so that validating a template which declares a `proxy_cache_path` does not take the running server's cache directory away from the user its workers run as.
It may also be desired to validate an nginx config outside of the deployment process. To do so, run the `nginx:validate-config` command. With no arguments, this will validate all app nginx configs, one at a time. A minimal wrapper nginx config is generated for each app's nginx config, upon which `nginx -t` will be run.
```shell
@@ -453,6 +491,8 @@ Changing these value globally or on a per-app basis will require rebuilding the
| proxy-buffering | `on` | string | Enables or disables buffering of responses from the proxied server |
| proxy-buffers | `8 8k` | string | Number and size of the buffers used for reading the proxied server response, for a single connection |
| proxy-busy-buffers-size | `16k` | string | Limits the total size of buffers that can be busy sending a response to the client while the response is not yet fully read. |
| proxy-http-version | `1.1` | string | The HTTP version to use for establishing a connection to your backend server. Ensure that your backend server supports this. |
| proxy-connect-timeout | `60s` | string | Timeout (with units) for establishing a connection to your backend server |
| proxy-keepalive | empty (disabled) | integer | Number of idle keepalive connections to upstream servers (disabled by default) |
| proxy-read-timeout | `60s` | string | Timeout (with units) for reading response from your backend server |
@@ -630,7 +670,7 @@ See the [proxy documentation](/docs/networking/proxy-management.md#regenerating-
All nginx-vhosts properties are settable at both the app and global scope.
| `access-log-format` | app + global | none | `--nginx-access-log-format`, `--nginx-global-access-log-format`, `--nginx-computed-access-log-format` | Custom nginx `log_format` directive used for the access log |
| `access-log-path` | app + global | _`{nginx-log-root}/{app}-access.log`_ | `--nginx-access-log-path`, `--nginx-global-access-log-path`, `--nginx-computed-access-log-path` | Path inside the nginx container where access logs are written |
| `bind-address-ipv4` | app + global | none | `--nginx-bind-address-ipv4`, `--nginx-global-bind-address-ipv4`, `--nginx-computed-bind-address-ipv4` | IPv4 address the nginx server block binds to |
@@ -639,6 +679,9 @@ All nginx-vhosts properties are settable at both the app and global scope.
| `client-header-timeout` | app + global | `60s` | `--nginx-client-header-timeout`, `--nginx-global-client-header-timeout`, `--nginx-computed-client-header-timeout` | Time allowed to read the request header from the client |
| `client-max-body-size` | app + global | `1m` | `--nginx-client-max-body-size`, `--nginx-global-client-max-body-size`, `--nginx-computed-client-max-body-size` | Maximum allowed request body size |
| `disable-custom-config` | app + global | `false` | `--nginx-disable-custom-config`, `--nginx-global-disable-custom-config`, `--nginx-computed-disable-custom-config` | When `true`, ignores app-supplied `nginx.conf.d/*.conf` snippets |
| `dns-resolver` | app + global | none | `--nginx-dns-resolver`, `--nginx-global-dns-resolver`, `--nginx-computed-dns-resolver` | Resolver address used for DNS-based upstreams. When unset, upstreams are IP-based |
| `dns-resolver-timeout` | app + global | `5s` | `--nginx-dns-resolver-timeout`, `--nginx-global-dns-resolver-timeout`, `--nginx-computed-dns-resolver-timeout` | Time nginx waits for a DNS response from the resolver |
| `dns-zone` | app + global | `docker` | `--nginx-dns-zone`, `--nginx-global-dns-zone`, `--nginx-computed-dns-zone` | Zone appended to `<app>.<process-type>` for DNS-based upstream hostnames |
| `error-log-path` | app + global | _`{nginx-log-root}/{app}-error.log`_ | `--nginx-error-log-path`, `--nginx-global-error-log-path`, `--nginx-computed-error-log-path` | Path inside the nginx container where error logs are written |
| `hsts` | app + global | `true` | `--nginx-hsts`, `--nginx-global-hsts`, `--nginx-computed-hsts` | When `true`, emits a `Strict-Transport-Security` header on HTTPS responses |
| `hsts-include-subdomains` | app + global | `true` | `--nginx-hsts-include-subdomains`, `--nginx-global-hsts-include-subdomains`, `--nginx-computed-hsts-include-subdomains` | Adds the `includeSubDomains` directive to the HSTS header |
@@ -652,6 +695,7 @@ All nginx-vhosts properties are settable at both the app and global scope.
| `proxy-buffers` | app + global | _`8 {pagesize}`_ | `--nginx-proxy-buffers`, `--nginx-global-proxy-buffers`, `--nginx-computed-proxy-buffers` | Number and size of buffers used for an upstream response |
| `proxy-busy-buffers-size` | app + global | _`2 * pagesize`_ | `--nginx-proxy-busy-buffers-size`, `--nginx-global-proxy-busy-buffers-size`, `--nginx-computed-proxy-busy-buffers-size` | Maximum buffer size that can be busy sending a response to the client |
| `proxy-http-version` | app + global | `1.1` | `--nginx-proxy-http-version`, `--nginx-global-proxy-http-version`, `--nginx-computed-proxy-http-version` | HTTP version to use for a connection to the upstream |
| `proxy-connect-timeout` | app + global | `60s` | `--nginx-proxy-connect-timeout`, `--nginx-global-proxy-connect-timeout`, `--nginx-computed-proxy-connect-timeout` | Time to establish a connection to the upstream |
| `proxy-keepalive` | app + global | none | `--nginx-proxy-keepalive`, `--nginx-global-proxy-keepalive`, `--nginx-computed-proxy-keepalive` | Number of idle keep-alive connections to upstream servers held open per worker |
| `proxy-read-timeout` | app + global | `60s` | `--nginx-proxy-read-timeout`, `--nginx-global-proxy-read-timeout`, `--nginx-computed-proxy-read-timeout` | Time to read a response from the upstream |
@@ -676,6 +720,6 @@ The following flag surfaces in `nginx:report` but is not managed by `nginx:set`:
The following properties are recorded internally by the plugin and are not exposed via `nginx:report`:
| Property | Description | Source |
|---|---|---|
|--- | --- | ---|
| `proxy-status` | `started`/`stopped` state of the nginx system service | `cmd-nginx-start`/`cmd-nginx-stop` |
| `nginx-conf-sigil-migrated` | Migration sentinel set the first time a v1 template is rewritten to the current schema | `plugins/nginx-vhosts/install` writes `"true"` once the install-time migration runs |
@@ -28,7 +28,44 @@ The OpenResty plugin has specific rules for routing requests:
- OpenResty integration is exposed via docker labels attached to containers. Changes in labels require either app deploys or rebuilds.
- While OpenResty will respect labels associated with other containers, only `web` containers have OpenResty labels injected by the plugin.
- Only `http:80` and `https:443` port mappings are supported at this time.
- Requests are routed as soon as the container is running and passing healthchecks.
- Requests are routed as soon as the container is running, before Dokku's healthchecks have passed. See the [healthchecks](#healthchecks) section for how failed requests to a container that is not yet listening are handled.
### Healthchecks
When an app has a readiness healthcheck defined in its `app.json` file with a `path` property, Dokku automatically generates OpenResty healthcheck labels. These configure OpenResty to periodically check each container and stop routing traffic to containers that fail the check until they pass again.
The following `app.json` healthcheck properties are mapped to OpenResty labels:
| `path` | `openresty.healthcheck-path` | The HTTP path to check (required) |
| `port` | `openresty.healthcheck-port` | The port to check |
| `scheme` | `openresty.healthcheck-scheme` | The scheme to use (`http` or `https`) |
| `timeout` | `openresty.healthcheck-timeout` | Timeout in seconds (defaults to `5`) |
| `wait` | `openresty.healthcheck-interval` | Interval between checks in seconds (defaults to `5`) |
| `attempts` | `openresty.healthcheck-fall` | Number of successive failed checks before a container is taken out of rotation (defaults to `3`) |
| `httpHeaders` | `openresty.healthcheck-header.<name>` | Headers to send with the check. A `Host` header is set via `openresty.healthcheck-host` instead |
If no `Host` header is specified, the first app domain is used, matching the header Dokku sends during deploy checks. Headers with invalid names or values containing quotes, backslashes, or control characters are skipped.
Additionally, the following labels are generated so that a request that fails to connect to a container is retried against another container, and the failing container is immediately taken out of rotation:
| OpenResty Label | Value | Description |
|-----------------|-------|-------------|
| `openresty.upstream-max-fails` | `1` | Number of failed requests before a container is taken out of rotation |
| `openresty.upstream-fail-timeout` | the healthcheck `wait` value, or `5s` | How long a container is taken out of rotation after a failed request |
| `openresty.proxy-next-upstream-timeout` | `5s` | How long a failed request may be retried against other containers |
OpenResty considers a new container healthy until it fails a check or a request to it fails to connect, and healthcheck state is reset whenever the OpenResty configuration is reloaded. Responses with a `2xx` or `3xx` status are considered healthy, and the `content` property is not supported.
The status of all checked upstreams can be viewed from within the OpenResty container:
> Only the first readiness healthcheck with a `path` property is used. Apps without one are routed as soon as the container is running, though requests that fail to connect to a container are still retried against other containers. See the [zero downtime deploys documentation](/docs/deployment/zero-downtime-deploys.md#healthchecks-and-label-based-proxies) for more information.
App-or-global properties expose three report flags: `--openresty-<property>` returns the raw per-app value (empty when unset), `--openresty-global-<property>` returns the raw global value (empty when unset), and `--openresty-computed-<property>` returns the effective value, resolving the per-app value first, then the global value, then the built-in default.
| `access-log-format` | app or global | none | `--openresty-access-log-format`, `--openresty-global-access-log-format`, `--openresty-computed-access-log-format` | Custom nginx `log_format` directive used for the access log |
| `access-log-path` | app or global | _`/var/log/nginx/{app}-access.log`_ | `--openresty-access-log-path`, `--openresty-global-access-log-path`, `--openresty-computed-access-log-path` | Path inside the openresty container where access logs are written |
| `allowed-letsencrypt-domains-func-base64` | global only | _allow-all stub_ | `--openresty-global-allowed-letsencrypt-domains-func-base64`, `--openresty-computed-allowed-letsencrypt-domains-func-base64` | Base64-encoded Lua function deciding which domains may request a letsencrypt certificate |
@@ -31,12 +31,12 @@ The Traefik plugin has specific rules for routing requests:
- If no `http:80` mapping is found, the first `http` port mapping is used for http requests.
- If no `https:443` mapping is found, the first `https` port mapping is used for https requests.
- If no `https` mapping is found, the container port from `http:80` will be used for https requests.
- Requests are routed as soon as the container is running and passing healthchecks.
- Requests are routed as soon as the container is running, before Dokku's healthchecks have passed.
- Readiness healthchecks defined in `app.json` with a `path` property are automatically transformed into Traefik healthcheck labels.
### Healthchecks
When an app has a readiness healthcheck defined in its `app.json` file with a `path` property, Dokku automatically generates Traefik healthcheck labels. These labels configure Traefik to perform health checks on the container before routing traffic to it.
When an app has a readiness healthcheck defined in its `app.json` file with a `path` property, Dokku automatically generates Traefik healthcheck labels. These labels configure Traefik to periodically check each container and stop routing traffic to containers that fail the check. Traefik considers a new container healthy until its first check fails, so requests may reach a container before its first check runs.
The following `app.json` healthcheck properties are mapped to Traefik labels:
@@ -67,7 +67,7 @@ Example `app.json` configuration:
```
> [!NOTE]
> Only the first readiness healthcheck with a `path` property is used. Liveness, startup, and command-based healthchecks are not transformed into Traefik labels.
> Only the first readiness healthcheck with a `path` property is used. Liveness, startup, and command-based healthchecks are not transformed into Traefik labels. See the [zero downtime deploys documentation](/docs/deployment/zero-downtime-deploys.md#healthchecks-and-label-based-proxies) for more information.
### Switching to Traefik
@@ -241,22 +241,22 @@ By default, Traefik uses TLS-ALPN-01 challenge for obtaining certificates. To sw
3. Configure the required environment variables for your DNS provider. Each DNS provider requires specific environment variables. The variable names should be prefixed with `dns-provider-`:
The `dns-provider-` prefix will be stripped and the variable name will be uppercased when passed to the Traefik container. For example, `dns-provider-cf_api_email` becomes `CF_API_EMAIL`.
| `api-enabled` | global only | `false` | `--traefik-global-api-enabled`, `--traefik-computed-api-enabled` | When `true`, enables the Traefik HTTP API |
| `api-entry-point` | global only | none | `--traefik-global-api-entry-point`, `--traefik-computed-api-entry-point` | Name of the entry point used by the Traefik API |
| `api-entry-point-address` | global only | none | `--traefik-global-api-entry-point-address`, `--traefik-computed-api-entry-point-address` | Address (`host:port`) the Traefik API listens on |
@@ -188,6 +188,95 @@ See the [port scheme documentation](/docs/networking/port-management.md#port-sch
See the [port management documentation](/docs/networking/port-management.md) for more information on how port mappings are managed for an application.
### Path-based routing to non-web processes
> [!IMPORTANT]
> New as of 0.39.0
By default, every HTTP/HTTPS request is routed to the `web` process declared in the app's `Procfile`. Use `proxy:route:*` to route specific path prefixes to a different process within the same app - for example, sending `/api/v0/*` to an `api` process or `/ws` to a `websocket` process. The `web` process always handles every other path as an implicit catch-all.
> [!NOTE]
> Path-based routing is rendered by the `nginx`, `traefik`, `caddy`, `openresty`, and `k3s` proxy backends. The `haproxy` backend accepts and stores route declarations but the routes are inert (no labels are emitted) because the `byjg/easy-haproxy` sidecar image only supports host-based routing. Routes stored ahead of a backend switch will activate automatically once the proxy is changed to a rendering backend.
#### Adding a route
```shell
# Route /api/v0/* to the api process on container port 5000
dokku proxy:route:set node-js-app api /api/v0
```
The `--port` flag overrides the default upstream port of `5000`:
```shell
dokku proxy:route:set node-js-app api /api/v0 --port 5001
```
The `--strip-prefix` flag strips the matched prefix before forwarding the request upstream. Use this when the process is written assuming it is mounted at root (`/users/42` instead of `/api/v0/users/42`):
```shell
dokku proxy:route:set node-js-app api /api/v0 --port 5001 --strip-prefix
```
`proxy:route:set` uses set semantics: the resulting route is fully described by its arguments. Omitting `--port` resets the port to `5000`; omitting `--strip-prefix` resets the flag to off. This makes the command safely re-runnable from declarative config tools such as [dokku/docket](https://github.com/dokku/docket).
The `web` process cannot be a route target since it serves the implicit `/` catch-all.
#### Removing a route
```shell
dokku proxy:route:remove node-js-app /api/v0
```
Removing a path that is not registered is a successful no-op.
-`GET /home` -> falls through to the `web` catch-all
#### Caveats per backend
- **nginx**: route changes take effect immediately on the next `proxy-build-config` reload (which `proxy:route:*` invokes automatically).
- **traefik**, **caddy**, and **openresty**: routes are applied via Docker labels on the target containers. Containers must be recreated for new or removed labels to take effect. The command prints a notice instructing you to run `dokku ps:rebuild <app>` to recreate containers.
- **k3s**: route changes take effect on the next deploy when the chart is re-rendered.
- **haproxy**: routes are stored but inert under this backend. They activate automatically if you switch to a rendering backend (`nginx`, `traefik`, `caddy`, `openresty`, or `k3s`).
- **WebSocket** traffic is forwarded transparently on `nginx`, `traefik`, `caddy`, and `k3s` backends - no additional configuration is required.
- A route to a process that is scaled to `0` will resolve to no upstream until the process is scaled up (`dokku ps:scale <app> <process>=1`). `proxy:route:set` warns when this is the case.
### Container network interface binding
> Changed as of 0.11.0
@@ -245,7 +334,7 @@ Finally, proxy implementations _may_ install extra software needed for the proxy
> The `Report flags` column lists the CLI argument names accepted by `proxy:report`. The JSON keys emitted by `proxy:report --format json` are the same names with the leading `--proxy-` stripped (e.g. `type`, `global-type`, `computed-type`). Legacy keys with the `proxy-` prefix (e.g. `proxy-type`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `disabled` | app only | `false` | `--proxy-disabled`, `--proxy-computed-disabled` (also exposed inverted as `--proxy-enabled`) | When `true`, disables proxy integration for this app (`proxy:enable`/`proxy:disable` write this) |
| `proxy-port` | app + global | none | `--proxy-proxy-port`, `--proxy-global-proxy-port`, `--proxy-computed-proxy-port` | Override port used for the HTTP listener in the generated proxy config |
| `proxy-ssl-port` | app + global | none | `--proxy-proxy-ssl-port`, `--proxy-global-proxy-ssl-port`, `--proxy-computed-proxy-ssl-port` | Override port used for the HTTPS listener in the generated proxy config |
@@ -31,7 +31,6 @@ One off containers are removed at the end of process execution.
In addition, one-off containers also run for a max of 24 hours (86400 seconds) by default, after which they are reaped. This can be changed by setting the `--ttl-seconds` argument:
```shell
# runs for 10 minutes
dokku run --ttl-seconds 600 node-js-app npm run mytask
@@ -52,6 +51,8 @@ console: bundle exec racksh
dokku run my-app console
```
The Procfile command is split into arguments using the same shell quoting rules used when deploying the app. See the [Procfile documentation](/docs/processes/process-management.md#procfile) for more details.
#### Specifying container labels
Containers may have specific labels attached. In order to avoid issues with dokku internals, do not use any labels beginning with either `com.dokku` or `org.label-schema`.
ps:inspect <app> # Displays a sanitized version of docker inspect for an app
ps:rebuild [--parallel count] [--all|<app>] # Rebuilds an app from source
ps:report [<app>] [<flag>] # Displays a process report for one or more apps
ps:restart [--parallel count] [--all|<app>] [<process-name>] # Restart an app
ps:restore [<app>] # Start previously running apps e.g. after reboot
ps:scale [--skip-deploy] [--format stdout|json] <app> [<proc>=<count>...] # Get/Set how many instances of a given process to run
ps:set <app> <key> <value> # Set or clear a ps property for an app
ps:start [--parallel count] [--all|<app>] # Start an app
ps:stop [--parallel count] [--all|<app>] # Stop an app
ps:inspect <app> # Displays a sanitized version of docker inspect for an app
ps:rebuild [--parallel count] [--no-cache] [--all|<app>] # Rebuilds an app from source
ps:report [<app>] [<flag>] # Displays a process report for one or more apps
ps:restart [--parallel count] [--all|<app>] [<process-name>] # Restart an app
ps:restore [<app>] # Start previously running apps e.g. after reboot
ps:scale [--skip-deploy] [--format stdout|json] <app> [<proc>=<count>...] # Get/Set how many instances of a given process to run
ps:scale --replace [--skip-deploy] <app> <proc>=<count> [<proc>=<count>...] # Replace the formation and scale unspecified process types to zero
ps:scale --clear [--skip-deploy] <app> # Reset the formation to the default scale
ps:set <app> <key> <value> # Set or clear a ps property for an app
ps:start [--parallel count] [--all|<app>] # Start an app
ps:stop [--parallel count] [--all|<app>] # Stop an app
```
## Usage
@@ -56,6 +58,29 @@ Finally, the number of parallel workers may be automatically set to the number o
dokku ps:rebuild --all --parallel -1
```
#### Rebuilding without the build cache
> [!IMPORTANT]
> New as of 0.38.34
An app may be rebuilt without using the build cache by specifying the `--no-cache` flag. This applies only to the triggered rebuild, and no setting is persisted for later deploys.
```shell
dokku ps:rebuild --no-cache node-js-app
```
The `--no-cache` flag may also be combined with the `--all` and `--parallel` flags.
```shell
dokku ps:rebuild --all --no-cache
```
The effect of the flag depends on the builder in use:
-`dockerfile`, `nixpacks`, and `railpack`: The build is run with `--no-cache`. For the `dockerfile` builder, [BuildKit directory caches](/docs/deployment/builders/dockerfiles.md#buildkit-directory-caching) are not cleared.
-`pack`: The build is run with `--clear-cache`.
-`herokuish`: The build cache is purged before the build, in the same way as `repo:purge-cache`.
A missing linked container will result in failure to boot apps. Services should all be started for apps being rebuilt.
### Restarting apps
@@ -125,7 +150,7 @@ The `ps` plugin provides a number of settings that can be used to managed deploy
| `stop-timeout` | Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands *also* respect this value. If not specified, the Docker defaults for the [`docker stop` command](https://docs.docker.com/engine/reference/commandline/stop/) will be used. | `30` |
| `stop-timeout` | Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands _also_ respect this value. If not specified, the Docker defaults for the [`docker stop` command](https://docs.docker.com/engine/reference/commandline/stop/) will be used. | `30` |
All settings can be set via the `ps:set` command. Using `stop-timeout` as an example:
If the app build declares an `ENTRYPOINT`, the command defined in the `Procfile` is passed as an argument to that entrypoint. This is the case for all Dockerfile-based, Docker Image, and Cloud Native Buildpack deployments.
When deploying via the `docker-local` scheduler, for all builders other than Herokuish, the command is split into arguments using shell quoting rules, but is not otherwise interpreted by a shell. Environment variable references such as `$PORT` are substituted before the command is split. Single quotes, double quotes, and backslash escapes then group and escape arguments, and any other special characters are passed to the container verbatim. Shell operators such as `&&`, `|`, `;`, and redirections are not supported and will fail the deploy, and a trailing `#` comment is ignored. If shell features are required, invoke a shell explicitly:
```Procfile
web: sh -c "bin/migrate && exec bin/server --port $PORT"
```
The `web` process type holds some significance in that it is the only process type that is automatically scaled to `1` on the initial application deploy. See the [web process scaling documentation](/docs/processes/process-management.md#the-web-process) for more details around scaling individual processes.
See the [Procfile location documentation](/docs/processes/process-management.md#changing-the-procfile-location) for more information on where to place your `Procfile` file.
@@ -208,7 +239,7 @@ Sometimes it may be desirable to set a different path for a given app, e.g. when
The value is the path to the desired file *relative* to the base search directory, and will never be treated as absolute paths in any context. If that file does not exist within the repository, Dokku will continue the build process as if the repository has no `Procfile`.
The value is the path to the desired file _relative_ to the base search directory, and will never be treated as absolute paths in any context. If that file does not exist within the repository, Dokku will continue the build process as if the repository has no `Procfile`.
The default value may be set by passing an empty value for the option:
@@ -246,12 +277,41 @@ Multiple process types can be scaled at once:
dokku ps:scale node-js-app web=1worker=1
```
> [!NOTE]
> Each argument must name a process type, and process counts must be zero or greater. Scaling a process type to `0` stops all of its containers, while a negative count is rejected.
If desired, the corresponding deploy will be skipped by using the `--skip-deploy` flag:
```shell
dokku ps:scale --skip-deploy node-js-app web=1
```
#### Replacing the formation
By default, `ps:scale` merges the specified process types into the existing formation, leaving any unspecified process type at its current count. The `--replace` flag instead treats the specified process types as the entire formation, setting the process count for every unspecified process type to zero:
```shell
dokku ps:scale --replace node-js-app web=1
```
An app scaled to `web=2 worker=3` before the above command will be scaled to `web=1 worker=0` after it, with the `worker` containers stopped as part of the same command.
> [!NOTE]
> At least one process type must be specified when using the `--replace` flag. Use the `--clear` flag to reset the formation instead.
#### Clearing the formation
The `--clear` flag resets the formation to the scale a newly created app has, a single `web` process, with the process count for every other process type set to zero:
```shell
dokku ps:scale --clear node-js-app
```
Apps with a `Procfile` that does not specify a `web` process type will have the process count for every process type set to zero.
> [!NOTE]
> Process types cannot be specified when using the `--clear` flag, nor can it be combined with the `--replace` flag.
#### Manually managing process scaling
> Using a `formation` key in an `app.json` file with _any_ `quantity` specified disables the ability to use `ps:scale` for scaling. All processes not specified in the `app.json` will have their process count set to zero.
> The `Report flags` column lists the CLI argument names accepted by `ps:report`. The JSON keys emitted by `ps:report --format json` are the same names with the leading `--ps-` stripped (e.g. `procfile-path`, `global-procfile-path`, `computed-procfile-path`). Legacy keys with the `ps-` prefix (e.g. `ps-procfile-path`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `dockerfile-start-cmd` | app only | none | `--ps-dockerfile-start-cmd`, `--ps-computed-dockerfile-start-cmd` | Override `CMD` for Dockerfile-based apps |
| `procfile-path` | app + global | `Procfile` | `--ps-procfile-path`, `--ps-global-procfile-path`, `--ps-computed-procfile-path` | Path to the app's Procfile, relative to the build root |
| `restore` | app only | `true` | `--restore` | When `true`, the app is restarted automatically by `ps:retire` after a host reboot |
| `restore` | app only | `true` | `--restore` | When `true`, the app is restarted automatically by `ps:restore` after a host reboot |
| `skip-deploy` | app + global | `false` | `--ps-skip-deploy`, `--ps-global-skip-deploy`, `--ps-computed-skip-deploy` | When `true`, skips the deploy phase after a successful build |
| `start-cmd` | app only | none | `--ps-start-cmd`, `--ps-computed-start-cmd` | Override start command for buildpack apps |
| `stop-timeout-seconds` | app + global | `30` | `--ps-stop-timeout-seconds`, `--ps-global-stop-timeout-seconds`, `--ps-computed-stop-timeout-seconds` | Seconds Docker waits before SIGKILLing a container on stop |
cron:list <app> [--format json|stdout] # List scheduled cron tasks for an app
cron:report [<app>] [<flag>] # Display report about an app
cron:resume <app> <cron_id> # Resume a cron task
cron:run <app> <cron_id> [--detach] # Run a cron task on the fly
cron:set [--global|<app>] <key> <value> # Set or clear a cron property for an app
cron:suspend <app> <cron_id> # Suspend a cron task
cron:list <app> [--format json|stdout] # List scheduled cron tasks for an app
cron:report [<app>] [<flag>] # Display report about an app
cron:resume <app> <cron_id> # Resume a cron task
cron:run <app> <cron_id> [--detach] [--ttl-seconds SECONDS] # Run a cron task on the fly
cron:set [--global|<app>] <key> <value> # Set or clear a cron property for an app
cron:suspend <app> <cron_id> # Suspend a cron task
```
## Usage
@@ -40,10 +40,9 @@ A cron task takes the following properties:
-`schedule`: A [cron-compatible](https://en.wikipedia.org/wiki/Cron#Overview) scheduling definition upon which to run the command. Seconds are generally not supported.
-`concurrency_policy`: A string (default: `allow`), that controls whether the cron task can be run concurrently with another invocation of itself. Valid options are `allow` (allow concurrency), `forbid` (exit the new cron task if there is an existing one), `replace` (delete any existing cron task and start the new one).
Zero or more cron tasks can be specified per app. Cron tasks are validated after the build artifact is created but before the app is deployed, and the cron schedule is updated during the post-deploy phase.
Cron tasks can run for a maximum of 24 hours via the docker-local scheduler, after which they are reaped from the system.
Cron tasks can run for a maximum of 24 hours, after which they are reaped from the system. The `docker-local` scheduler reaps expired tasks via the `dokku ps:retire` pass that runs every 5 minutes, so a task may overrun its deadline by up to 5 minutes. The `k3s` scheduler enforces the deadline directly through the job's `activeDeadlineSeconds`.
See the [app.json location documentation](/docs/advanced-usage/deployment-tasks.md#changing-the-appjson-location) for more information on where to place your `app.json` file.
@@ -56,6 +55,7 @@ When running scheduled cron tasks, there are a few items to be aware of:
- At this time, only the `PATH` and `SHELL` environment variables are specified in the cron template.
- A `MAILTO` value can be set via the `cron:set` command.
- A `MAILFROM` value can be set via the `cron:set` command.
- Tasks injected by plugins via the [`cron-entries` trigger](/docs/development/plugin-triggers.md#cron-entries) may set a `MAILTO` value of their own, which is used for that task instead of the global value. `MAILTO` is reset after the task to the global value, or to the `dokku` user when no global value is set, so no other task is mailed to that address. The output of such a task is mailed to that address as well as appended to the task's log file.
- Each scheduled task is executed within a one-off `run` container, and thus inherit any docker-options specified for `run` containers. Resources are never shared between scheduled tasks.
- Scheduled cron tasks are supported on a per-scheduler basis. Schedulers that use the host crontab - such as `docker-local` - have their `app.json` cron tasks written to the `dokku` user crontab, while schedulers that manage their own cron backend - such as `k3s` - schedule them natively.
- Tasks for _all_ apps managed by a host-crontab scheduler such as `docker-local` are written to a single crontab file owned by the `dokku` user. The `dokku` user's crontab should be considered reserved for this purpose.
@@ -190,6 +190,14 @@ By default, the task is run in an attached container - as supported by the sched
An on-the-fly invocation runs for a maximum of 24 hours (86400 seconds), the same deadline scheduled invocations receive. A different deadline can be requested with the `--ttl-seconds` argument:
The value applies only to that invocation - tasks started by the schedule keep the 24 hour default.
All one-off cron executions have their containers terminated after invocation.
#### Displaying reports
@@ -330,7 +338,7 @@ SHELL=/bin/bash
> The `Report flags` column lists the CLI argument names accepted by `cron:report`. The JSON keys emitted by `cron:report --format json` are the same names with the leading `--cron-` stripped (e.g. `global-mailto`, `computed-mailto`, `maintenance`). Legacy keys with the `cron-` prefix (e.g. `cron-global-mailto`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release.
| `mailfrom` | global only | none | `--cron-global-mailfrom`, `--cron-computed-mailfrom` | `From:` address used in cron failure emails |
| `mailto` | global only | none | `--cron-global-mailto`, `--cron-computed-mailto` | Recipient address for cron failure emails; empty disables email |
| `maintenance` | app + global | `false` | `--cron-maintenance`, `--cron-global-maintenance`, `--cron-computed-maintenance` | When `true`, suspends all cron tasks for the app (or globally) |
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.