The usage strings for `scheduler-k3s:report` and `scheduler-k3s:set` omitted the `--global` option, which is the only way to reach the scheduler-wide report since a bare `scheduler-k3s:report` iterates every app, and `:report` also omitted `--format stdout|json`. The command listing in the k3s documentation is resynced with the help output, which additionally restores flags that had been dropped from `scheduler-k3s:cluster:add`, `scheduler-k3s:cluster:list`, and `scheduler-k3s:initialize`.
Install steps run in alphabetical order of the enabled plugin directory, so `apps`, `builder`, and `checks` read an app's environment before the `config` plugin had moved the `ENV` file to its new location. The read came back empty, so their deprecated `DOKKU_*` variables were never migrated to the matching plugin property and were never unset, with nothing reported either way: `dokku config:show` kept listing the variable while the plugin behaved as though it were unset. The relocation now runs before any deprecated variable is read, whatever the install order, and each old file is removed as soon as it has been drained rather than on a later install, which also covers the global file that was never removed at all. A file that reappears at the old path can only have been written by hand, so it is merged in with a warning naming its keys instead of being discarded.
Options drained out of the pre-0.38.0 `DOCKER_OPTIONS_<PHASE>` files were copied verbatim rather than re-serialized the way `docker-options:add` stores them, so `docker-options:remove` compared the canonical string it builds against a stored value that could never match it and exited successfully without removing anything. Removal now matches stored options by shell word, and stored options are rewritten into the canonical form once on upgrade, which additionally splits an entry that carried several flags on a single line into one entry per flag so a single flag can be removed and so the readers that match on a flag prefix see one value per entry. The leftover `.migrated` sentinel drain is restored to running ahead of the global short-circuit that had made it unreachable, and the plugin's Go tests are added to the test target that had never run them.
Sysctls the kernel does not namespace, such as `vm.max_map_count`, cannot be set from a pod spec and previously had no answer beyond editing `/etc/sysctl.d` on each host by hand. `scheduler-k3s:node-sysctls:set` now applies them through a privileged daemonset, which reaches nodes joined later and reapplies after a reboot. Sysctls may be scoped to a node profile, with a profile scope inheriting the global values and overriding them on conflict so that every node is covered by exactly one daemonset. Clearing a sysctl stops dokku managing it but does not restore the previous value, which persists until the node reboots.
The `docker-local` scheduler supports `--sysctl` for free because docker options are passed verbatim to `docker run`, but the k3s scheduler silently dropped it. Namespaced sysctls now render into the pod's `securityContext.sysctls` for deployments, cron jobs, and one-off runs. A sysctl the kernel does not namespace fails the deploy instead of being dropped, since it cannot take effect within a pod regardless of what was requested.
Node profiles controlled how a node joined the cluster but left no trace on the node afterwards, so a profile could not be selected against with `kubectl`, a `nodeSelector`, or a node affinity rule. Nodes joined without a profile are left unlabeled, and the server node created by `scheduler-k3s:initialize` never carries the label since it does not pass through `scheduler-k3s:cluster:add`.
The server node created by `scheduler-k3s:initialize` had no way to receive kubelet arguments, unlike nodes joined through `scheduler-k3s:cluster:add` or configured via `scheduler-k3s:profiles:add`. This meant settings such as `allowed-unsafe-sysctls` were unreachable on a single-node install.
Host-crontab generation for `app.json` cron tasks now lives in the `cron` plugin, gated by a new `scheduler-uses-host-cron` trigger that the `docker-local` scheduler answers true while self-managed schedulers such as `k3s` answer false. This lets any host-cron scheduler participate in normal `app.json` cron without coupling to `scheduler-docker-local` or duplicating the crontab writer, while the `k3s` scheduler continues to manage its own in-cluster cron jobs.
Closes#8862.
The `issuer.yaml` chart template dereferenced `.Values.global.issuer.enabled` without guarding against the value being absent, which yaml serialization omitted for apps without a per-app email, causing a nil-pointer render error that broke every k3s web deploy.
The `letsencrypt-email-prod` and `letsencrypt-email-stag` properties can now be set per app in addition to globally, resolving app-level before the global value for the app's selected `letsencrypt-server`. An app that sets its own email renders a namespaced cert-manager `Issuer` using that email, while apps without an override continue to use the shared `ClusterIssuer` with the global email.
Values supplied through docker options, `--ttl-seconds`, and `-e` flowed into a Bash `eval` during build, deploy, and run, letting a low-privileged user execute arbitrary commands on the host as the dokku user. These arguments are now tokenized and passed through to the container verbatim, without shell expansion. A one-time migration repairs stored labels whose backticks were saved with a stray backslash so Traefik-style rules stay valid on the next deploy.
# History
## 0.38.24
Install/update via the bootstrap script:
```shell
wget -NP . https://dokku.com/install/v0.38.24/bootstrap.sh
sudo DOKKU_TAG=v0.38.24 bash bootstrap.sh
```
### Documentation
- #8836: @josegonzalez Link herokuish buildpack references to buildpack management page
### Tests
- #8841: @dependabot[bot] chore(deps): bump ruby from 4.0.5 to 4.0.6 in /tests/apps/dockerfile-entrypoint
- #8843: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.82.0 to 1.82.1 in /tests/apps/gogrpc
### Dependencies
- #8845: @dependabot[bot] chore(deps): bump github.com/mattn/go-isatty from 0.0.22 to 0.0.23 in /plugins/app-json
- #8844: @dependabot[bot] chore(deps): bump github.com/melbahja/goph from 1.5.1 to 1.5.2 in /plugins/common
- #8842: @dependabot[bot] chore(deps): bump timberio/vector from 0.56.0-debian to 0.57.0-debian in /plugins/logs
- #8838: @dokku-bot chore: bump pack to 0.40.8
- #8846: @dependabot[bot] chore(deps): bump traefik from v3.7.7 to v3.7.8 in /plugins/traefik-vhosts
- #8839: @dependabot[bot] chore(deps): bump actions/setup-node from 6 to 7
- #8834: @dokku-bot chore: bump dokku-event-listener to 0.20.0
# History
## 0.38.23
Install/update via the bootstrap script:
```shell
wget -NP . https://dokku.com/install/v0.38.23/bootstrap.sh
sudo DOKKU_TAG=v0.38.23 bash bootstrap.sh
```
### Bug Fixes
- #8833: @josegonzalez Parse cert CN and subject on OpenSSL 3.x
### Tests
- #8825: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/zombies-dockerfile-no-tini
- #8823: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/zombies-dockerfile-tini
- #8820: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 292 to 293 in /tests/apps/php
- #8821: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/go-fail-predeploy
- #8822: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/gogrpc
- #8824: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/go-fail-postdeploy
### Dependencies
- #8831: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.21.2 to 3.21.3 in /plugins/scheduler-k3s
- #8830: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.37.0 to 1.38.0 in /plugins/scheduler-k3s
- #8826: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.53.0 to 0.54.0 in /plugins/common
- #8827: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.20.3 to 1.21.0 in /plugins/scheduler-k3s
- #8828: @dependabot[bot] chore(deps): bump traefik from v3.7.6 to v3.7.7 in /plugins/traefik-vhosts
- #8829: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.24.0 to 1.0.0 in /plugins/scheduler-k3s
The `certs` plugin extracted a certificate's Common Name and formatted its subject using string assumptions that only held for pre-3.x OpenSSL output, so a certificate with only a Common Name and no Subject Alternative Name reported no hostnames from `certs:report` and was not recognized during nginx config generation, while the subject report retained the `subject=` prefix and used the wrong separators. Normalizing the subject with `-nameopt` before parsing makes the extraction version independent across OpenSSL and LibreSSL.