diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fa2343c69..bfebbf371 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,143 +9,23 @@ updates: labels: - "type: dependencies" - package-ecosystem: gomod - directory: "/plugins/app-json" + directories: + - "/plugins/*" schedule: interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/apps" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/builder" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/buildpacks" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/common" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/config" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/cron" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/docker-options" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/logs" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/network" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/nginx-vhosts" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/ports" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/proxy" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/ps" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/registry" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/repo" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/resource" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/scheduler-docker-local" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/scheduler-k3s" - schedule: - interval: daily - open-pull-requests-limit: 2 - labels: - - "type: dependencies" - - package-ecosystem: gomod - directory: "/plugins/scheduler" - schedule: - interval: daily - open-pull-requests-limit: 2 + open-pull-requests-limit: 10 + groups: + kubernetes: + patterns: + - "k8s.io/*" + - "sigs.k8s.io/*" + - "helm.sh/*" + plugins: + group-by: dependency-name + exclude-patterns: + - "k8s.io/*" + - "sigs.k8s.io/*" + - "helm.sh/*" labels: - "type: dependencies" - package-ecosystem: "docker" diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index e16596c0a..5d0e3b14d 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -35,3 +35,8 @@ MD024: false # The issue template doesn't have one # https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md041 MD041: false + +# Table column style +# The docs pad tables loosely rather than aligning every pipe +# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md060 +MD060: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44720039c..4d9642710 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,6 +219,19 @@ jobs: name: coverage.${{ matrix.index }} path: test-results/coverage + sudo-preserve-env: + name: sudo-preserve-env + runs-on: ubuntu-26.04 + needs: check-commit + if: ${{ needs.check-commit.outputs.skip != 'true' }} + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v7 + + - name: check that the preserved environment survives sudo-rs + run: ./tests/ci/sudo_preserve_env.sh + publish-test-results: name: publish-test-results needs: unit-tests diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 092b9acee..ca9dbeee9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -38,9 +38,9 @@ jobs: cache: 'npm' cache-dependency-path: '.github/workflows/lint.yml' - name: Install markdownlint-cli - run: npm install -g markdownlint-cli@0.35.0 + run: npm install -g markdownlint-cli@0.50.0 - name: Run markdown-lint - run: markdownlint -c .github/linters/.markdown-lint.yml *.md **/*.md + run: markdownlint -c .github/linters/.markdown-lint.yml --ignore tests/apps '**/*.md' packer: name: packer diff --git a/HISTORY.md b/HISTORY.md index 09e498cad..48ad2dd60 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,373 @@ # 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 + +## 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 + +## 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 + +## 0.38.31 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.31/bootstrap.sh +sudo DOKKU_TAG=v0.38.31 bash bootstrap.sh +``` + +### Bug Fixes + +- #9084: @josegonzalez Report stored node-sysctls images globally +- #9082: @josegonzalez Verify existing apps in git commands +- #9081: @josegonzalez Validate run:logs arguments +- #9080: @josegonzalez Check app directory when creating a new app +- #9079: @josegonzalez Validate app name in app-scoped commands +- #9077: @josegonzalez Respect quoting in procfile commands and remove support for bare shell operators +- #9076: @josegonzalez Validate app.json healthcheck path and scheme + +### Dependencies + +- #9078: @dependabot[bot] chore(deps): bump pyparsing from 3.3.2 to 3.3.3 in /docs/_build + +## 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 + +## 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 + +## 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 + ## 0.38.27 Install/update via the bootstrap script: @@ -8442,7 +8810,7 @@ sudo DOKKU_TAG=v0.13.1 bash bootstrap.sh - #3145: @josegonzalez Add report trigger - #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! ### Bug Fixes diff --git a/Makefile b/Makefile index f9d32cc31..60dfe0e14 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ DOKKU_VERSION ?= master -TARGETARCH ?= amd64 +include common.mk DOCKER_IMAGE_LABELER_URL ?= $(shell jq -r --arg name docker-image-labeler --arg arch $(TARGETARCH) '.dependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json) DOCKER_CONTAINER_HEALTHCHECKER_URL ?= $(shell jq -r --arg name docker-container-healthchecker --arg arch $(TARGETARCH) '.dependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json) @@ -31,9 +31,7 @@ else BUILD_STACK_TARGETS = build-in-docker endif -include common.mk - -.PHONY: all apt-update install version copyfiles copyplugin man-db plugins dependencies docker-image-labeler lambda-builder netrc sshcommand procfile-util plugn docker aufs stack count vagrant-acl-add vagrant-dokku go-build +.PHONY: all apt-update install version copyfiles copyplugin man-db plugins dependencies docker-image-labeler lambda-builder netrc sshcommand procfile-util plugn docker aufs stack count vagrant-acl-add vagrant-dokku go-build wget include docs.mk include tests.mk @@ -126,17 +124,20 @@ else endif plugin-dependencies: plugn procfile-util - sudo -E dokku plugin:install-dependencies --core + sudo dokku plugin:install-dependencies --core plugins: plugn procfile-util docker - sudo -E dokku plugin:install --core + sudo dokku plugin:install --core -dependencies: apt-update jq docker-image-labeler docker-container-healthchecker lambda-builder netrc sshcommand plugn procfile-util docker help2man man-db sigil dos2unix parallel +dependencies: apt-update wget jq docker-image-labeler docker-container-healthchecker lambda-builder netrc sshcommand plugn procfile-util docker help2man man-db sigil dos2unix parallel $(MAKE) -e stack apt-update: apt-get update -qq +wget: + apt-get -qq -y --no-install-recommends install wget + parallel: apt-get -qq -y --no-install-recommends install parallel @@ -152,7 +153,7 @@ help2man: man-db: apt-get -qq -y --no-install-recommends install man-db -docker-image-labeler: +docker-image-labeler: wget wget -qO /usr/local/bin/docker-image-labeler ${DOCKER_IMAGE_LABELER_URL} chmod +x /usr/local/bin/docker-image-labeler diff --git a/README.md b/README.md index 600f2a8ee..e00cad286 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Support us with a monthly donation and help us continue our activities. [[Become A fresh VM running any of the following operating systems: -- Ubuntu 22.04 / 24.04 (amd64/arm64) - Any currently supported release +- Ubuntu 22.04 / 24.04 / 26.04 (amd64/arm64) - Any currently supported release - Debian 11+ (amd64/arm64) An SSH keypair that can be used for application deployment. If this exists before installation, it will be automatically imported into dokku. @@ -85,8 +85,8 @@ Otherwise, you will need to import the keypair manually after installation using To install the latest stable release, run the following commands as a user who has access to `sudo`: ```shell -wget -NP . https://dokku.com/install/v0.38.27/bootstrap.sh -sudo DOKKU_TAG=v0.38.27 bash bootstrap.sh +wget -NP . https://dokku.com/install/v0.38.34/bootstrap.sh +sudo DOKKU_TAG=v0.38.34 bash bootstrap.sh ``` 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. diff --git a/Vagrantfile b/Vagrantfile index 1c4552eaa..543a094be 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -9,7 +9,7 @@ DOKKU_IP = ENV["DOKKU_IP"] || "10.0.0.2" PREBUILT_STACK_URL = File.exist?("#{File.dirname(__FILE__)}/stack.tgz") ? 'file:///root/dokku/stack.tgz' : nil PUBLIC_KEY_PATH = "#{Dir.home}/.ssh/id_rsa.pub" -make_cmd = "TARGETARCH=$(dpkg --print-architecture) DEBIAN_FRONTEND=noninteractive make -e install" +make_cmd = "DEBIAN_FRONTEND=noninteractive make -e install" if PREBUILT_STACK_URL make_cmd = "PREBUILT_STACK_URL='#{PREBUILT_STACK_URL}' #{make_cmd}" end diff --git a/bootstrap.sh b/bootstrap.sh index b3653a756..cca2e0715 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,18 +1,48 @@ #!/usr/bin/env bash +# shellcheck disable=SC2329 set -eo pipefail [[ $TRACE ]] && set -x # A script to bootstrap dokku. -# It expects to be run on Ubuntu 22.04/24.04 via 'sudo` +# It expects to be run on Ubuntu 22.04/24.04/26.04 or Debian 11/12/13 via 'sudo' # If installing a tag higher than 0.3.13, it may install dokku via a package (so long as the package is higher than 0.3.13) -# It checks out the dokku source code from GitHub into ~/dokku and then runs 'make install' from dokku source. +# Otherwise it checks out the dokku source code from GitHub into ~/dokku and then runs 'make install' from dokku source. +# +# Output modes: +# - fancy: stdout is a terminal; shows a spinner and a live tail of command output for the running step +# - plain: stdout is not a terminal (CI, Vagrant, piping to a file); one line when each step starts and ends +# - verbose: DOKKU_VERBOSE=1 or TRACE=1; streams all command output +# All command output is also written to a timestamped log file under /var/log/dokku. +# Set NO_COLOR=1 to disable colors. # We wrap this whole script in functions, so that we won't execute # until the entire script is downloaded. # That's good because it prevents our output overlapping with wget's. # It also means that we can't run a partially downloaded script. -SUPPORTED_VERSIONS="Debian [11, 12, 13], Ubuntu [22.04, 24.04]" +SUPPORTED_VERSIONS="Debian [11, 12, 13], Ubuntu [22.04, 24.04, 26.04]" +DOKKU_DOCS_URL="https://dokku.com/docs" + +UI_MODE="plain" +UI_COLOR="false" +UI_UTF8="false" +UI_WIDTH=80 +UI_TAIL_LINES=4 +UI_TMP="" +UI_LOG_FILE="" +UI_NOW=0 +UI_DURATION="" +UI_SANITIZED="" +UI_INSTALL_START=0 +UI_STEP_INDEX=0 +UI_STEP_TOTAL=0 +UI_STEP_TITLE="" +UI_STEP_START=0 +UI_STEP_PID="" +UI_FRAME=0 +UI_FRAME_DRAWN="false" +UI_STEP_NAMES=() +UI_STEP_DURATIONS=() log-fail() { declare desc="log fail formatter" @@ -20,154 +50,8 @@ log-fail() { exit 1 } -ensure-environment() { - local FREE_MEMORY - if [[ -z "$DOKKU_TAG" ]]; then - echo "Preparing to install $DOKKU_REPO..." - else - echo "Preparing to install $DOKKU_TAG from $DOKKU_REPO..." - fi - - hostname -f &>/dev/null || { - log-fail "This installation script requires that you have a hostname set for the instance. Please set a hostname for 127.0.0.1 in your /etc/hosts" - } - - FREE_MEMORY=$(grep MemTotal /proc/meminfo | awk '{print $2}') - if [[ "$FREE_MEMORY" -lt 1003600 ]]; then - echo "For dokku to build containers, it is strongly suggested that you have 1024 megabytes or more of free memory" - echo "If necessary, please consult this document to setup swap: https://dokku.com/docs/getting-started/advanced-installation/#vms-with-less-than-1-gb-of-memory" - fi -} - -install-requirements() { - echo "--> Ensuring we have the proper dependencies" - - case "$DOKKU_DISTRO" in - debian) - if ! dpkg -l | grep -q gpg-agent; then - apt-get update -qq >/dev/null - apt-get -qq -y --no-install-recommends install gpg-agent - fi - if ! command -v lsb_release &>/dev/null; then - apt-get update -qq >/dev/null - apt-get -qq -y --no-install-recommends install lsb-release - fi - if [[ "$DOKKU_DISTRO_VERSION" -lt "13" ]]; then - if ! dpkg -l | grep -q software-properties-common; then - apt-get update -qq >/dev/null - apt-get -qq -y --no-install-recommends install software-properties-common - fi - fi - ;; - ubuntu) - if ! dpkg -l | grep -q gpg-agent; then - apt-get update -qq >/dev/null - apt-get -qq -y --no-install-recommends install gpg-agent - fi - if ! dpkg -l | grep -q software-properties-common; then - apt-get update -qq >/dev/null - apt-get -qq -y --no-install-recommends install software-properties-common - fi - - add-apt-repository -y universe >/dev/null - apt-get update -qq >/dev/null - ;; - esac -} - -install-dokku() { - if ! command -v dokku &>/dev/null; then - echo "--> Note: Installing dokku for the first time will result in removal of" - echo " files in the nginx 'sites-enabled' directory. Please manually" - echo " restore any files that may be removed after the installation and" - echo " web setup is complete." - echo "" - echo " Installation will continue in 10 seconds." - sleep 10 - fi - - if [[ -n $DOKKU_BRANCH ]]; then - install-dokku-from-source "origin/$DOKKU_BRANCH" - elif [[ -n $DOKKU_TAG ]]; then - local DOKKU_SEMVER="${DOKKU_TAG//v/}" - major=$(echo "$DOKKU_SEMVER" | awk '{split($0,a,"."); print a[1]}') - minor=$(echo "$DOKKU_SEMVER" | awk '{split($0,a,"."); print a[2]}') - patch=$(echo "$DOKKU_SEMVER" | awk '{split($0,a,"."); print a[3]}') - - use_plugin=false - # 0.4.0 implemented a `plugin` plugin - if [[ "$major" -eq "0" ]] && [[ "$minor" -ge "4" ]] && [[ "$patch" -ge "0" ]]; then - use_plugin=true - elif [[ "$major" -ge "1" ]]; then - use_plugin=true - fi - - # 0.3.13 was the first version with a debian package - if [[ "$major" -eq "0" ]] && [[ "$minor" -eq "3" ]] && [[ "$patch" -ge "13" ]]; then - install-dokku-from-package "$DOKKU_SEMVER" - echo "--> Running post-install dependency installation" - dokku plugins-install-dependencies - elif [[ "$use_plugin" == "true" ]]; then - install-dokku-from-package "$DOKKU_SEMVER" - echo "--> Running post-install dependency installation" - sudo -E dokku plugin:install-dependencies --core - else - install-dokku-from-source "$DOKKU_TAG" - fi - else - install-dokku-from-package - echo "--> Running post-install dependency installation" - sudo -E dokku plugin:install-dependencies --core - fi -} - -install-dokku-from-source() { - local DOKKU_CHECKOUT="$1" - - if ! command -v apt-get &>/dev/null; then - log-fail "This installation script requires apt-get. For manual installation instructions, consult https://dokku.com/docs/getting-started/advanced-installation/" - fi - - apt-get -qq -y --no-install-recommends install sudo git make - case "$DOKKU_DISTRO" in - debian) - if [[ "$DOKKU_DISTRO_VERSION" -lt "13" ]]; then - if ! dpkg -l | grep -q software-properties-common; then - apt-get -qq -y --no-install-recommends install software-properties-common - fi - fi - ;; - ubuntu) - if ! dpkg -l | grep -q software-properties-common; then - apt-get -qq -y --no-install-recommends install software-properties-common - fi - ;; - esac - - cd /root - if [[ ! -d /root/dokku ]]; then - git clone "$DOKKU_REPO" /root/dokku - fi - - cd /root/dokku - git fetch origin - [[ -n $DOKKU_CHECKOUT ]] && git checkout "$DOKKU_CHECKOUT" - make install -} - -install-dokku-from-package() { - case "$DOKKU_DISTRO" in - debian | ubuntu) - install-dokku-from-deb-package "$@" - ;; - *) - log-fail "Unsupported Linux distribution. For manual installation instructions, consult https://dokku.com/docs/getting-started/advanced-installation/" - ;; - esac -} - in-array() { - declare desc="return true if value ($1) is in list (all other arguments)" + declare desc="return true if value (\$1) is in list (all other arguments)" local e for e in "${@:2}"; do @@ -176,74 +60,998 @@ in-array() { return 1 } -install-dokku-from-deb-package() { - local DOKKU_CHECKOUT="$1" - local NO_INSTALL_RECOMMENDS=${DOKKU_NO_INSTALL_RECOMMENDS:=""} - local OS_ID - - if ! in-array "$DOKKU_DISTRO_VERSION" "22.04" "24.04" "10" "11" "12" "13"; then - log-fail "Unsupported Linux distribution. Only the following versions are supported: $SUPPORTED_VERSIONS" +ui-now() { + declare desc="store the current time in microseconds in UI_NOW" + UI_NOW="${EPOCHREALTIME/[.,]/}" + if [[ -z "$UI_NOW" ]]; then + UI_NOW="$(date +%s)000000" fi +} + +ui-format-duration() { + declare desc="store a human readable form of a microsecond duration in UI_DURATION" + local micros="$1" style="$2" + local tenths=$((micros / 100000)) seconds=$((micros / 1000000)) + + if [[ "$seconds" -lt 60 ]]; then + if [[ "$style" == "whole" ]]; then + UI_DURATION="${seconds}s" + else + UI_DURATION="$((tenths / 10)).$((tenths % 10))s" + fi + elif [[ "$seconds" -lt 3600 ]]; then + printf -v UI_DURATION '%dm %02ds' $((seconds / 60)) $((seconds % 60)) + else + printf -v UI_DURATION '%dh %02dm' $((seconds / 3600)) $((seconds % 3600 / 60)) + fi +} + +ui-color() { + declare desc="print the escape sequence for a color role at a color depth" + local role="$1" depth="$2" truecolor xterm basic + + case "$role" in + primary) truecolor="42;143;189" xterm=31 basic=34 ;; + teal) truecolor="7;203;207" xterm=44 basic=36 ;; + muted) truecolor="151;166;181" xterm=109 basic=2 ;; + success) truecolor="51;204;51" xterm=77 basic=32 ;; + warning) truecolor="255;215;0" xterm=220 basic=33 ;; + error) truecolor="236;99;80" xterm=203 basic=31 ;; + esac + + case "$depth" in + 24) printf '\033[38;2;%sm' "$truecolor" ;; + 256) printf '\033[38;5;%sm' "$xterm" ;; + *) printf '\033[%sm' "$basic" ;; + esac +} + +ui-color-depth() { + declare desc="print the color depth supported by the terminal" + local colors + + case "$COLORTERM" in + truecolor | 24bit) + echo 24 + return + ;; + esac + + case "$TERM" in + *-direct* | xterm-ghostty | xterm-kitty | alacritty* | wezterm* | foot*) + echo 24 + return + ;; + *256color*) + echo 256 + return + ;; + esac + + colors="$(tput colors 2>/dev/null || echo 0)" + if [[ "$colors" -ge 256 ]]; then + echo 256 + else + echo 16 + fi +} + +ui-init-colors() { + declare desc="set the color escape sequences for the current terminal" + local depth + + C_RESET="" C_BOLD="" C_PRIMARY="" C_TEAL="" C_MUTED="" C_SUCCESS="" C_WARNING="" C_ERROR="" + [[ "$UI_COLOR" == "true" ]] || return 0 + + depth="$(ui-color-depth)" + C_RESET=$'\033[0m' + C_BOLD=$'\033[1m' + C_PRIMARY="$(ui-color primary "$depth")" + C_TEAL="$(ui-color teal "$depth")" + C_MUTED="$(ui-color muted "$depth")" + C_SUCCESS="$(ui-color success "$depth")" + C_WARNING="$(ui-color warning "$depth")" + C_ERROR="$(ui-color error "$depth")" +} + +ui-update-width() { + declare desc="store the terminal width in UI_WIDTH" + local size + + size="$({ stty size /dev/null || true)" + UI_WIDTH="${size#* }" + if [[ ! "$UI_WIDTH" =~ ^[0-9]+$ ]] || [[ "$UI_WIDTH" -lt 20 ]]; then + UI_WIDTH="${COLUMNS:-80}" + fi +} + +ui-init() { + declare desc="pick the output mode, colors, glyphs, and log file" + local mode="$1" charset="${LC_ALL:-${LC_CTYPE:-$LANG}}" + + if [[ -n "$mode" ]]; then + UI_MODE="$mode" + elif [[ -n "$TRACE" ]] || [[ -n "$DOKKU_VERBOSE" && "$DOKKU_VERBOSE" != "0" && "$DOKKU_VERBOSE" != "false" ]]; then + UI_MODE="verbose" + elif [[ -t 1 ]] && [[ -n "$TERM" ]] && [[ "$TERM" != "dumb" ]]; then + UI_MODE="fancy" + fi + + if [[ "$UI_MODE" != "plain" ]] && [[ -t 1 ]] && [[ -z "$NO_COLOR" ]] && [[ "$TERM" != "dumb" ]]; then + UI_COLOR="true" + fi + + shopt -s nocasematch + if [[ "$charset" =~ utf-?8 ]]; then + UI_UTF8="true" + fi + shopt -u nocasematch + + if [[ "$UI_UTF8" == "true" ]]; then + UI_SPINNER=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏") + UI_GLYPH_ELLIPSIS="…" + else + UI_SPINNER=("|" "/" "-" "\\") + UI_GLYPH_ELLIPSIS="..." + fi + + ui-init-colors + ui-update-width + UI_TMP="$(mktemp -d)" + ui-init-log + ui-now + UI_INSTALL_START="$UI_NOW" + + trap ui-cleanup EXIT + trap ui-interrupt INT TERM + if [[ "$UI_MODE" == "fancy" ]]; then + trap ui-update-width WINCH + fi +} + +ui-init-log() { + declare desc="create the install log file" + local name + + name="install-$(date +%Y%m%d-%H%M%S).log" + if mkdir -p /var/log/dokku 2>/dev/null && : >"/var/log/dokku/$name" 2>/dev/null; then + UI_LOG_FILE="/var/log/dokku/$name" + else + UI_LOG_FILE="${TMPDIR:-/tmp}/dokku-$name" + : >"$UI_LOG_FILE" + fi +} + +ui-log() { + declare desc="write a timestamped line to the install log" + TZ=UTC0 printf '%(%Y-%m-%dT%H:%M:%SZ)T %s\n' -1 "$*" >>"$UI_LOG_FILE" +} + +ui-cleanup() { + declare desc="stop any running step and restore the terminal" + ui-kill-step + if [[ "$UI_MODE" == "fancy" ]]; then + printf '\033[?25h' + fi + if [[ -n "$UI_TMP" ]]; then + rm -rf "$UI_TMP" + fi +} + +ui-interrupt() { + declare desc="handle ctrl-c and termination signals" + trap - INT TERM + ui-kill-step + if [[ "$UI_MODE" == "fancy" ]] && [[ "$UI_FRAME_DRAWN" == "true" ]]; then + printf '\r\033[J' + fi + ui-log "==> install interrupted" + ui-error "Installation interrupted" + ui-error "Full log: $UI_LOG_FILE" + exit 130 +} + +ui-process-tree() { + declare desc="print a process and all of its descendants" + local pid="$1" child children="" + + echo "$pid" + children="$(cat /proc/"$pid"/task/*/children 2>/dev/null || pgrep -P "$pid" 2>/dev/null || true)" + for child in $children; do + ui-process-tree "$child" + done +} + +ui-kill-step() { + declare desc="kill the running step and everything it started" + local pid pids=() child + + [[ -n "$UI_STEP_PID" ]] || return 0 + for child in $(cat /proc/$$/task/*/children 2>/dev/null || pgrep -P $$ 2>/dev/null || true); do + while read -r pid; do + pids+=("$pid") + done < <(ui-process-tree "$child") + done + if [[ "${#pids[@]}" -gt 0 ]]; then + kill "${pids[@]}" 2>/dev/null || true + fi + UI_STEP_PID="" +} + +ui-info1() { + declare desc="print a step line in the style of dokku_log_info1" + printf '%s----->%s %s\n' "$C_PRIMARY" "$C_RESET" "$*" +} + +ui-info2() { + declare desc="print a heading in the style of dokku_log_info2" + printf '%s=====>%s %s\n' "$C_PRIMARY" "$C_RESET" "$*" +} + +ui-verbose() { + declare desc="print an indented line in the style of dokku_log_verbose" + printf ' %s\n' "$*" +} + +ui-muted() { + declare desc="print an indented, muted line" + printf ' %s%s%s\n' "$C_MUTED" "$*" "$C_RESET" +} + +ui-warn() { + declare desc="print a warning in the style of dokku_log_warn" + printf '%s ! %s%s\n' "$C_WARNING" "$*" "$C_RESET" +} + +ui-error() { + declare desc="print an error in the style of dokku_log_fail" + printf '%s ! %s%s\n' "$C_ERROR" "$*" "$C_RESET" +} + +ui-header() { + declare desc="print the installer header" + local target="$1" os="$2" + + ui-info2 "${C_BOLD}Installing $target on $os${C_RESET}" + ui-muted "Log file: $UI_LOG_FILE" + ui-log "==> installing $target on $os" +} + +ui-truncate() { + declare desc="truncate text to a maximum number of characters" + local text="$1" max="$2" + + if [[ "$max" -le 0 ]]; then + echo "" + elif [[ "${#text}" -gt "$max" ]]; then + echo "${text:0:max-1}${UI_GLYPH_ELLIPSIS:0:1}" + else + echo "$text" + fi +} + +ui-step-name() { + declare desc="print the counter and title of the running step" + printf '%s[%s/%s]%s %s%s%s' "$C_MUTED" "$UI_STEP_INDEX" "$UI_STEP_TOTAL" "$C_RESET" "$C_BOLD" "$UI_STEP_TITLE" "$C_RESET" +} + +ui-draw-frame() { + declare desc="draw the running step and its live output" + local spinner lines=() line width + + ui-now + ui-format-duration $((UI_NOW - UI_STEP_START)) whole + spinner="${UI_SPINNER[UI_FRAME % ${#UI_SPINNER[@]}]}" + UI_FRAME=$((UI_FRAME + 1)) + width=$((UI_WIDTH - 1)) + + if [[ "$UI_TAIL_LINES" -gt 0 ]] && [[ -s "$UI_TMP/step.tail" ]]; then + mapfile -t lines < <(tail -n "$UI_TAIL_LINES" "$UI_TMP/step.tail") + fi + + printf '\r%s----%s%s ' "$C_PRIMARY" "$spinner" "$C_RESET" + ui-step-name + printf ' %s(%s)%s\033[K' "$C_MUTED" "$UI_DURATION" "$C_RESET" + for line in "${lines[@]}"; do + line="$(ui-truncate "$line" $((width - 7)))" + printf '\n %s%s%s\033[K' "$C_MUTED" "$line" "$C_RESET" + done + printf '\033[J' + + if [[ "${#lines[@]}" -gt 0 ]]; then + printf '\033[%dA' "${#lines[@]}" + fi + printf '\r' + UI_FRAME_DRAWN="true" +} + +ui-stream() { + declare desc="timestamp step output into the log, the live tail, and verbose output" + local start="$1" line display + + while IFS= read -r line || [[ -n "$line" ]]; do + line="${line%$'\r'}" + line="${line##*$'\r'}" + ui-sanitize "$line" + display="$UI_SANITIZED" + ui-now + ui-format-duration $((UI_NOW - start)) + TZ=UTC0 printf '%(%Y-%m-%dT%H:%M:%SZ)T %s\n' -1 "$line" >>"$UI_LOG_FILE" + printf '%s\n' "$display" >>"$UI_TMP/step.raw" + printf '+%s %s\n' "$UI_DURATION" "$display" >>"$UI_TMP/step.tail" + if [[ "$UI_MODE" == "verbose" ]]; then + printf ' %s+%s%s %s\n' "$C_MUTED" "$UI_DURATION" "$C_RESET" "$display" + fi + done +} + +ui-sanitize() { + declare desc="store a line without escape sequences or control characters in UI_SANITIZED" + local line="$1" pattern=$'\033''\[[0-9;?]*[A-Za-z]' + + line="${line//$'\t'/ }" + while [[ "$line" =~ $pattern ]]; do + line="${line/"${BASH_REMATCH[0]}"/}" + done + UI_SANITIZED="${line//[[:cntrl:]]/}" +} + +ui-result() { + declare desc="record a piece of the finished step's detail text" + echo "$*" >>"$UI_TMP/step.result" +} + +ui-note() { + declare desc="record a warning shown under the current step" + echo "$*" >>"$UI_TMP/step.notes" +} + +ui-summary-note() { + declare desc="record a warning shown at the end of the install" + local line + for line in "$@"; do + echo "$line" >>"$UI_TMP/summary.notes" + done + echo "" >>"$UI_TMP/summary.notes" +} + +ui-run() { + declare desc="print a command and run it" + echo "\$ $*" + "$@" +} + +ui-action() { + declare desc="run a command as a timed part of a step" + local label="$1" start + shift + + ui-now + start="$UI_NOW" + "$@" + ui-now + ui-format-duration $((UI_NOW - start)) + ui-result "$label $UI_DURATION" +} + +ui-join() { + declare desc="join lines from a file with commas" + local file="$1" line output="" + + [[ -s "$file" ]] || return 0 + while IFS= read -r line; do + if [[ -z "$output" ]]; then + output="$line" + else + output="$output, $line" + fi + done <"$file" + echo "$output" +} + +ui-step() { + declare desc="run a function as a numbered install step" + local title="$1" detail="$2" rc=1 duration result line + shift 2 + + UI_STEP_INDEX=$((UI_STEP_INDEX + 1)) + UI_STEP_TITLE="$title" + UI_FRAME=0 + UI_FRAME_DRAWN="false" + : >"$UI_TMP/step.tail" + : >"$UI_TMP/step.raw" + : >"$UI_TMP/step.result" + : >"$UI_TMP/step.notes" + rm -f "$UI_TMP/step.rc" + + ui-log "==> step start [$UI_STEP_INDEX/$UI_STEP_TOTAL] $title" + if [[ "$UI_MODE" == "fancy" ]]; then + printf '\033[?25l' + else + ui-info1 "$(ui-step-name)" + fi + + ui-now + UI_STEP_START="$UI_NOW" + { + set +e + ( + set -e + "$@" + ) &1 + echo "$?" >"$UI_TMP/step.rc" + } | ui-stream "$UI_STEP_START" & + UI_STEP_PID="$!" + + if [[ "$UI_MODE" == "fancy" ]]; then + while kill -0 "$UI_STEP_PID" 2>/dev/null; do + ui-draw-frame + sleep 0.1 + done + fi + wait "$UI_STEP_PID" || true + UI_STEP_PID="" + if [[ -s "$UI_TMP/step.rc" ]]; then + rc="$(<"$UI_TMP/step.rc")" + fi + + ui-now + ui-format-duration $((UI_NOW - UI_STEP_START)) + duration="$UI_DURATION" + UI_STEP_NAMES+=("$title") + UI_STEP_DURATIONS+=($((UI_NOW - UI_STEP_START))) + ui-log "==> step end [$UI_STEP_INDEX/$UI_STEP_TOTAL] $title ($duration, exit $rc)" + result="$(ui-join "$UI_TMP/step.result")" + result="${result:-$detail}" + + if [[ "$UI_MODE" == "fancy" ]]; then + printf '\r\033[J\033[?25h' + if [[ "$rc" == "0" ]]; then + ui-info1 "$(ui-step-name) $C_MUTED($duration)$C_RESET" + [[ -z "$result" ]] || ui-muted "$result" + fi + elif [[ "$rc" == "0" ]]; then + ui-muted "${result:+$result }($duration)" + fi + + if [[ -s "$UI_TMP/step.notes" ]]; then + while IFS= read -r line; do + ui-warn "$line" + done <"$UI_TMP/step.notes" + fi + + if [[ "$rc" != "0" ]]; then + ui-fail "$title" "$duration" + fi +} + +ui-fail() { + declare desc="print the output of a failed step and exit" + local title="$1" duration="$2" line + + ui-error "[$UI_STEP_INDEX/$UI_STEP_TOTAL] $title failed after $duration. Output from this step:" + while IFS= read -r line; do + ui-verbose "$line" + done <"$UI_TMP/step.raw" + ui-error "Full log: $UI_LOG_FILE" + ui-error "Help: $DOKKU_DOCS_URL/getting-started/troubleshooting/" + ui-log "==> install failed" + exit 1 +} + +ui-countdown() { + declare desc="show a warning and wait before continuing" + local short="$1" seconds="$2" remaining line count=0 + shift 2 + + ui-log "==> notice: $short (waiting ${seconds}s)" + ui-warn "$1" + for line in "${@:2}"; do + printf '%s %s%s\n' "$C_WARNING" "$line" "$C_RESET" + count=$((count + 1)) + done + + if [[ "$UI_MODE" != "fancy" ]]; then + printf '%s Continuing in %ss...%s\n' "$C_WARNING" "$seconds" "$C_RESET" + sleep "$seconds" + return 0 + fi + + printf '\033[?25l' + for ((remaining = seconds; remaining > 0; remaining--)); do + printf '\r%s Continuing in %ss%s%s\033[K' "$C_WARNING" "$remaining" "$UI_GLYPH_ELLIPSIS" "$C_RESET" + sleep 1 + done + printf '\033[%dA\r\033[J' $((count + 1)) + ui-warn "$short Waited ${seconds}s." + printf '\033[?25h' +} + +ui-slowest-steps() { + declare desc="print the three slowest steps" + local i j output="" picked=() slowest + + for ((j = 0; j < 3 && j < ${#UI_STEP_NAMES[@]}; j++)); do + slowest=-1 + for i in "${!UI_STEP_NAMES[@]}"; do + in-array "$i" "${picked[@]}" && continue + if [[ "$slowest" -lt 0 ]] || [[ "${UI_STEP_DURATIONS[i]}" -gt "${UI_STEP_DURATIONS[slowest]}" ]]; then + slowest="$i" + fi + done + picked+=("$slowest") + ui-format-duration "${UI_STEP_DURATIONS[slowest]}" + [[ -z "$output" ]] || output+=", " + output+="${UI_STEP_NAMES[slowest]} $UI_DURATION" + done + echo "$output" +} + +ui-summary() { + declare desc="print the install summary" + local version="$1" domain="$2" keys="$3" host="$4" total line first="true" + + ui-now + ui-format-duration $((UI_NOW - UI_INSTALL_START)) + total="$UI_DURATION" + + ui-info2 "${C_TEAL}${C_BOLD}Dokku $version installed in $total${C_RESET}" + ui-verbose "$(printf '%-15s %s' "Global domain:" "${domain:-not set}")" + ui-verbose "$(printf '%-15s %s' "SSH keys:" "${keys:-none}")" + ui-verbose "$(printf '%-15s %s' "Slowest steps:" "$(ui-slowest-steps)")" + ui-verbose "$(printf '%-15s %s' "Log file:" "$UI_LOG_FILE")" + ui-info2 "Deploy an app" + ui-verbose "git remote add dokku dokku@$host:my-app" + ui-verbose "git push dokku main" + + if [[ -s "$UI_TMP/summary.notes" ]]; then + while IFS= read -r line; do + if [[ -z "$line" ]]; then + first="true" + elif [[ "$first" == "true" ]]; then + ui-warn "$line" + first="false" + else + ui-verbose "$line" + fi + done <"$UI_TMP/summary.notes" + fi + ui-log "==> install complete ($total)" +} + +fetch-url() { + declare desc="download a url to stdout with curl or wget" + local url="$1" + + if command -v curl &>/dev/null; then + curl -fsSL "$url" + elif command -v wget &>/dev/null; then + wget -nv -O - "$url" + else + log-fail "This installation script requires curl or wget" + fi +} + +package-installed() { + declare desc="return true if a debian package is installed" + local package="$1" + dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q "ok installed" +} + +docker-version() { + declare desc="print the installed docker version" + local version + + version="$(docker --version 2>/dev/null || true)" + version="${version#Docker version }" + version="${version%%,*}" + echo "v$version" +} + +dokku-os-id() { + declare desc="print the apt codename used for the dokku package repository" + local os_id + + os_id="$(lsb_release -cs 2>/dev/null || echo "noble")" + if ! in-array "$DOKKU_DISTRO" "debian" "ubuntu" "raspbian"; then + echo "noble" + elif [[ "$DOKKU_DISTRO" == "ubuntu" ]]; then + in-array "$os_id" "jammy" "noble" && echo "$os_id" || echo "noble" + else + in-array "$os_id" "bullseye" "bookworm" "trixie" && echo "$os_id" || echo "bookworm" + fi +} + +required-packages() { + declare desc="print the packages the installer needs on this distribution" + case "$DOKKU_DISTRO" in + debian) + if [[ "$DOKKU_DISTRO_VERSION" -lt "13" ]]; then + echo "gpg-agent lsb-release software-properties-common" + else + echo "gpg-agent lsb-release" + fi + ;; + ubuntu) + echo "gpg-agent software-properties-common" + ;; + esac +} + +resolve-install-method() { + declare desc="decide whether to install from a package or from source" + local semver major minor patch use_plugin=false + + DOKKU_INSTALL_METHOD="package" + DOKKU_PACKAGE_VERSION="" + DOKKU_SOURCE_CHECKOUT="" + DOKKU_DEPENDENCIES_COMMAND="plugin:install-dependencies" + + if [[ -n $DOKKU_BRANCH ]]; then + DOKKU_INSTALL_METHOD="source" + DOKKU_SOURCE_CHECKOUT="origin/$DOKKU_BRANCH" + return 0 + fi + + [[ -n $DOKKU_TAG ]] || return 0 + + semver="${DOKKU_TAG//v/}" + major=$(echo "$semver" | awk '{split($0,a,"."); print a[1]}') + minor=$(echo "$semver" | awk '{split($0,a,"."); print a[2]}') + patch=$(echo "$semver" | awk '{split($0,a,"."); print a[3]}') + + if [[ "$major" -eq "0" ]] && [[ "$minor" -ge "4" ]] && [[ "$patch" -ge "0" ]]; then + use_plugin=true + elif [[ "$major" -ge "1" ]]; then + use_plugin=true + fi + + if [[ "$major" -eq "0" ]] && [[ "$minor" -eq "3" ]] && [[ "$patch" -ge "13" ]]; then + DOKKU_PACKAGE_VERSION="$semver" + DOKKU_DEPENDENCIES_COMMAND="plugins-install-dependencies" + elif [[ "$use_plugin" == "true" ]]; then + DOKKU_PACKAGE_VERSION="$semver" + else + DOKKU_INSTALL_METHOD="source" + DOKKU_SOURCE_CHECKOUT="$DOKKU_TAG" + fi +} + +check-environment() { + declare desc="check the host for install requirements" + local memory + + hostname -f &>/dev/null || { + log-fail "This installation script requires that you have a hostname set for the instance. Please set a hostname for 127.0.0.1 in your /etc/hosts" + } + echo "hostname: $(hostname -f)" + + if [[ "$DOKKU_INSTALL_METHOD" == "package" ]]; then + if ! in-array "$DOKKU_DISTRO_VERSION" "22.04" "24.04" "26.04" "10" "11" "12" "13"; then + log-fail "Unsupported Linux distribution. Only the following versions are supported: $SUPPORTED_VERSIONS" + fi + fi + + memory=$(grep MemTotal /proc/meminfo | awk '{print $2}') + echo "memory: $memory kB" + if [[ "$memory" -lt 1003600 ]]; then + ui-note "Less than 1024MB of memory. Builds may fail without swap." + ui-summary-note "Less than 1024MB of memory. Add swap before building apps:" "$DOKKU_DOCS_URL/getting-started/advanced-installation/#vms-with-less-than-1-gb-of-memory" + fi +} + +enable-universe() { + declare desc="enable the ubuntu universe repository" + ui-run add-apt-repository -y universe + ui-run apt-get update -qq +} + +install-requirements() { + declare desc="install the packages the installer depends on" + local missing=() package + + for package in $(required-packages); do + if [[ "$package" == "lsb-release" ]]; then + command -v lsb_release &>/dev/null || missing+=("$package") + elif ! package-installed "$package"; then + missing+=("$package") + fi + done + + if [[ "${#missing[@]}" -gt 0 ]]; then + ui-action "apt update" ui-run apt-get update -qq + ui-action "packages" ui-run apt-get -qq -y --no-install-recommends install "${missing[@]}" + else + ui-result "packages already installed" + fi + + if [[ "$DOKKU_DISTRO" == "ubuntu" ]]; then + ui-action "universe" enable-universe + fi +} + +install-docker() { + declare desc="install docker via get.docker.com" + if command -v docker &>/dev/null; then + echo "docker already installed: $(docker --version)" + ui-result "already installed $(docker-version)" + return 0 + fi + + export CHANNEL=stable + echo "\$ fetch https://get.docker.com/ | sh" + fetch-url https://get.docker.com/ | sh + ui-result "$(docker-version)" + ui-result "get.docker.com" +} + +add-dokku-repository() { + declare desc="add the dokku apt repository" + local os_id + + case "$DOKKU_DISTRO" in + debian | ubuntu) ;; + *) + log-fail "Unsupported Linux distribution. For manual installation instructions, consult $DOKKU_DOCS_URL/getting-started/advanced-installation/" + ;; + esac + + os_id="$(dokku-os-id)" + echo "\$ fetch https://packagecloud.io/dokku/dokku/gpgkey > /etc/apt/trusted.gpg.d/dokku.asc" + fetch-url https://packagecloud.io/dokku/dokku/gpgkey | sudo tee /etc/apt/trusted.gpg.d/dokku.asc + echo "deb https://packagecloud.io/dokku/dokku/$DOKKU_DISTRO/ $os_id main" | tee /etc/apt/sources.list.d/dokku.list + ui-run apt-get update -qq +} + +install-dokku-package() { + declare desc="install the dokku debian package" + local version="$1" + local NO_INSTALL_RECOMMENDS=${DOKKU_NO_INSTALL_RECOMMENDS:=""} if [[ -n $DOKKU_DOCKERFILE ]]; then NO_INSTALL_RECOMMENDS=" --no-install-recommends " fi - if ! command -v docker &>/dev/null; then - echo "--> Installing docker" - if uname -r | grep -q linode; then - echo "--> NOTE: Using Linode? Docker may complain about missing AUFS support." - echo " You can safely ignore this warning." - echo "" - echo " Installation will continue in 10 seconds." - sleep 10 - fi - export CHANNEL=stable - wget -nv -O - https://get.docker.com/ | sh + if [[ -n $DOKKU_VHOST_ENABLE ]]; then + echo "dokku dokku/vhost_enable boolean $DOKKU_VHOST_ENABLE" | sudo debconf-set-selections + fi + if [[ -n $DOKKU_HOSTNAME ]]; then + echo "dokku dokku/hostname string $DOKKU_HOSTNAME" | sudo debconf-set-selections + fi + if [[ -n $DOKKU_SKIP_KEY_FILE ]]; then + echo "dokku dokku/skip_key_file boolean $DOKKU_SKIP_KEY_FILE" | sudo debconf-set-selections + fi + if [[ -n $DOKKU_KEY_FILE ]]; then + echo "dokku dokku/key_file string $DOKKU_KEY_FILE" | sudo debconf-set-selections + fi + if [[ -n $DOKKU_NGINX_ENABLE ]]; then + echo "dokku dokku/nginx_enable string $DOKKU_NGINX_ENABLE" | sudo debconf-set-selections fi - OS_ID="$(lsb_release -cs 2>/dev/null || echo "noble")" - if ! in-array "$DOKKU_DISTRO" "debian" "ubuntu" "raspbian"; then - DOKKU_DISTRO="ubuntu" - OS_ID="noble" - fi - - if [[ "$DOKKU_DISTRO" == "ubuntu" ]]; then - OS_IDS=("jammy" "noble") - if ! in-array "$OS_ID" "${OS_IDS[@]}"; then - OS_ID="noble" - fi - elif [[ "$DOKKU_DISTRO" == "debian" ]]; then - OS_IDS=("bullseye" "bookworm" "trixie") - if ! in-array "$OS_ID" "${OS_IDS[@]}"; then - OS_ID="bookworm" - fi - elif [[ "$DOKKU_DISTRO" == "raspbian" ]]; then - OS_IDS=("bullseye" "bookworm" "trixie") - if ! in-array "$OS_ID" "${OS_IDS[@]}"; then - OS_ID="bookworm" - fi - fi - - echo "--> Installing dokku" - wget -qO- https://packagecloud.io/dokku/dokku/gpgkey | sudo tee /etc/apt/trusted.gpg.d/dokku.asc - echo "deb https://packagecloud.io/dokku/dokku/$DOKKU_DISTRO/ $OS_ID main" | tee /etc/apt/sources.list.d/dokku.list - apt-get update -qq >/dev/null - - [[ -n $DOKKU_VHOST_ENABLE ]] && echo "dokku dokku/vhost_enable boolean $DOKKU_VHOST_ENABLE" | sudo debconf-set-selections - [[ -n $DOKKU_HOSTNAME ]] && echo "dokku dokku/hostname string $DOKKU_HOSTNAME" | sudo debconf-set-selections - [[ -n $DOKKU_SKIP_KEY_FILE ]] && echo "dokku dokku/skip_key_file boolean $DOKKU_SKIP_KEY_FILE" | sudo debconf-set-selections - [[ -n $DOKKU_KEY_FILE ]] && echo "dokku dokku/key_file string $DOKKU_KEY_FILE" | sudo debconf-set-selections - [[ -n $DOKKU_NGINX_ENABLE ]] && echo "dokku dokku/nginx_enable string $DOKKU_NGINX_ENABLE" | sudo debconf-set-selections - - if [[ -n $DOKKU_CHECKOUT ]]; then - apt-get -qq -y $NO_INSTALL_RECOMMENDS install "dokku=$DOKKU_CHECKOUT" + if [[ -n $version ]]; then + ui-run apt-get -qq -y $NO_INSTALL_RECOMMENDS install "dokku=$version" else - apt-get -qq -y $NO_INSTALL_RECOMMENDS install dokku + ui-run apt-get -qq -y $NO_INSTALL_RECOMMENDS install dokku fi } +install-dokku-dependencies() { + declare desc="install the dependencies of the core dokku plugins" + if [[ "$DOKKU_DEPENDENCIES_COMMAND" == "plugins-install-dependencies" ]]; then + ui-run dokku plugins-install-dependencies + else + ui-run sudo dokku plugin:install-dependencies --core + fi +} + +install-source-requirements() { + declare desc="install the packages needed to build dokku from source" + if ! command -v apt-get &>/dev/null; then + log-fail "This installation script requires apt-get. For manual installation instructions, consult $DOKKU_DOCS_URL/getting-started/advanced-installation/" + fi + + ui-run apt-get -qq -y --no-install-recommends install sudo git make + case "$DOKKU_DISTRO" in + debian) + if [[ "$DOKKU_DISTRO_VERSION" -lt "13" ]] && ! package-installed software-properties-common; then + ui-run apt-get -qq -y --no-install-recommends install software-properties-common + fi + ;; + ubuntu) + if ! package-installed software-properties-common; then + ui-run apt-get -qq -y --no-install-recommends install software-properties-common + fi + ;; + esac +} + +clone-dokku-source() { + declare desc="clone the dokku repository and check out the requested ref" + local checkout="$1" + + cd /root + if [[ ! -d /root/dokku ]]; then + ui-run git clone "$DOKKU_REPO" /root/dokku + fi + + cd /root/dokku + ui-run git fetch origin + if [[ -n $checkout ]]; then + ui-run git checkout "$checkout" + fi + ui-result "$(git rev-parse --short HEAD)" +} + +build-dokku-source() { + declare desc="build and install dokku from source" + cd /root/dokku + ui-run make install +} + +primary-ip() { + declare desc="print the primary ipv4 address of the server" + local route + + route="$(ip -4 route get 1.1.1.1 2>/dev/null || true)" + if [[ "$route" =~ src\ ([0-9.]+) ]]; then + echo "${BASH_REMATCH[1]}" + return 0 + fi + route="$(hostname -I 2>/dev/null || true)" + echo "${route%% *}" +} + +add-ssh-keys() { + declare desc="add the keys from a file as admin keys" + local key_file="$1" line key name count=0 + + while IFS= read -r line || [[ -n "$line" ]]; do + if [[ "$line" =~ ((ssh-(rsa|ed25519|dss)|ecdsa-sha2-[a-z0-9]+|sk-[a-z0-9@.-]+)\ [A-Za-z0-9+/=]+) ]]; then + key="${BASH_REMATCH[1]}" + count=$((count + 1)) + name="admin" + [[ "$count" -eq 1 ]] || name="admin-$count" + echo "\$ dokku ssh-keys:add $name" + if ! echo "$key" | dokku ssh-keys:add "$name"; then + echo "skipping a key from $key_file that could not be added" + fi + fi + done <"$key_file" +} + +set-global-domain() { + declare desc="set the global domain to the server's ip" + local ip="$1" + ui-run dokku domains:set-global "$ip" +} + +configure-dokku() { + declare desc="add ssh keys and a global domain when they are missing" + local dokku_root="${DOKKU_ROOT:-/home/dokku}" key_file="/root/.ssh/authorized_keys" ip + + if [[ -s "$dokku_root/.ssh/authorized_keys" ]]; then + echo "ssh keys already present, skipping" + ui-result "ssh keys already set" + elif [[ "$DOKKU_SKIP_KEY_FILE" == "true" ]]; then + echo "DOKKU_SKIP_KEY_FILE is set, skipping ssh keys" + ui-result "admin key skipped" + elif [[ ! -s "$key_file" ]]; then + echo "no keys found in $key_file, skipping" + ui-result "admin key skipped" + else + ui-action "admin key" add-ssh-keys "$key_file" + echo "$key_file" >"$UI_TMP/ssh-key-source" + fi + + if [[ -s "$dokku_root/VHOST" ]]; then + echo "global domain already set to $(<"$dokku_root/VHOST")" + ui-result "global domain already set" + else + ip="$(primary-ip)" + if [[ -n "$ip" ]]; then + ui-action "global domain" set-global-domain "$ip" + else + ui-result "global domain skipped" + fi + fi +} + +ssh-key-names() { + declare desc="print the names of the keys allowed to push to dokku" + local dokku_root="${DOKKU_ROOT:-/home/dokku}" line names=() + + [[ -f "$dokku_root/.ssh/authorized_keys" ]] || return 0 + while IFS= read -r line; do + if [[ "$line" =~ NAME=\\\"([^\\]+)\\\" ]]; then + names+=("${BASH_REMATCH[1]}") + fi + done <"$dokku_root/.ssh/authorized_keys" + echo "${names[*]}" +} + +print-summary() { + declare desc="print the ready summary with what was configured" + local dokku_root="${DOKKU_ROOT:-/home/dokku}" version domain="" keys host + + version="$(dokku version 2>/dev/null || true)" + version="v${version##* }" + if [[ -s "$dokku_root/VHOST" ]]; then + domain="$(<"$dokku_root/VHOST")" + fi + + keys="$(ssh-key-names)" + if [[ -n "$keys" ]] && [[ -s "$UI_TMP/ssh-key-source" ]]; then + keys="$keys (from $(<"$UI_TMP/ssh-key-source"))" + elif [[ -z "$keys" ]]; then + ui-summary-note "No SSH key was added. Add the key you will deploy with:" "echo 'CONTENTS_OF_ID_RSA_PUB_FILE' | dokku ssh-keys:add admin" + fi + + if [[ -z "$domain" ]]; then + ui-summary-note "No global domain is set. Set one to route apps by name:" "dokku domains:set-global dokku.me" + fi + + host="${domain:-$(primary-ip)}" + ui-summary "$version" "$domain" "$keys" "${host:-SERVER_IP}" +} + +nginx-notice() { + declare desc="warn that a first time install removes nginx sites" + if command -v dokku &>/dev/null; then + return 0 + fi + ui-countdown "First install removes nginx sites-enabled files." 10 \ + "Installing Dokku for the first time removes files in nginx's sites-enabled" \ + "directory. Restore any removed files after the installation is complete." +} + +linode-notice() { + declare desc="warn linode users about aufs messages from docker" + if command -v docker &>/dev/null || ! uname -r | grep -q linode; then + return 0 + fi + ui-countdown "Docker may warn about missing AUFS support on Linode." 10 \ + "Using Linode? Docker may complain about missing AUFS support." \ + "You can safely ignore this warning." +} + +install-target() { + declare desc="print what is being installed for the header" + if [[ -n $DOKKU_BRANCH ]]; then + echo "Dokku from branch $DOKKU_BRANCH" + elif [[ -n $DOKKU_TAG ]]; then + echo "Dokku $DOKKU_TAG" + else + echo "the latest Dokku" + fi +} + +os-description() { + declare desc="print the distribution and architecture" + local name arch + + name="$(. /etc/os-release && echo "$NAME $VERSION_ID")" + arch="$(dpkg --print-architecture 2>/dev/null || uname -m)" + echo "$name ($arch)" +} + +environment-detail() { + declare desc="print the detail text for the environment step" + local memory arch + + memory=$(grep MemTotal /proc/meminfo | awk '{print $2}') + arch="$(dpkg --print-architecture 2>/dev/null || uname -m)" + if [[ "$memory" -ge 1048576 ]]; then + memory="$(((memory + 524288) / 1048576))GB" + else + memory="$((memory / 1024))MB" + fi + echo "$DOKKU_DISTRO $DOKKU_DISTRO_VERSION, $arch, $memory" +} + main() { + declare desc="install dokku" + local docker_detail="" version_detail + export DOKKU_DISTRO DOKKU_DISTRO_VERSION DOKKU_DISTRO=$(. /etc/os-release && echo "$ID") DOKKU_DISTRO_VERSION=$(. /etc/os-release && echo "$VERSION_ID") @@ -251,13 +1059,40 @@ main() { export DEBIAN_FRONTEND=noninteractive export DOKKU_REPO=${DOKKU_REPO:-"https://github.com/dokku/dokku.git"} - ensure-environment - install-requirements - install-dokku + resolve-install-method + ui-init + ui-header "$(install-target)" "$(os-description)" - if [[ -f /etc/update-motd.d/99-dokku ]]; then - /etc/update-motd.d/99-dokku || true + if [[ "$DOKKU_INSTALL_METHOD" == "package" ]]; then + UI_STEP_TOTAL=7 + else + UI_STEP_TOTAL=6 fi + + ui-step "Checking environment" "$(environment-detail)" check-environment + ui-step "Installing required packages" "$(required-packages)" install-requirements + + if [[ "$DOKKU_INSTALL_METHOD" == "package" ]]; then + command -v docker &>/dev/null || docker_detail="get.docker.com" + linode-notice + ui-step "Installing Docker Engine" "$docker_detail" install-docker + ui-step "Adding Dokku apt repository" "packagecloud, $(dokku-os-id)" add-dokku-repository + nginx-notice + version_detail="${DOKKU_PACKAGE_VERSION:+v$DOKKU_PACKAGE_VERSION}" + ui-step "Installing Dokku" "${version_detail:-latest}" install-dokku-package "$DOKKU_PACKAGE_VERSION" + ui-step "Installing plugin dependencies" "core plugins" install-dokku-dependencies + else + ui-step "Installing build tools" "sudo git make" install-source-requirements + ui-step "Cloning Dokku" "${DOKKU_SOURCE_CHECKOUT:-$DOKKU_REPO}" clone-dokku-source "$DOKKU_SOURCE_CHECKOUT" + nginx-notice + ui-step "Building Dokku" "make install" build-dokku-source + fi + + ui-step "Configuring Dokku" "" configure-dokku + print-summary } -main "$@" +{ + main "$@" + exit $? +} diff --git a/common.mk b/common.mk index ce25bd997..7d834c81e 100644 --- a/common.mk +++ b/common.mk @@ -1,11 +1,14 @@ GO_ARGS ?= GO_PLUGIN_MAKE_TARGET ?= build GO_REPO_ROOT := /go/src/github.com/dokku/dokku -BUILD_IMAGE := hub.diyla.com/golang:1.26.2 +BUILD_IMAGE := hub.diyla.com/golang:1.27.1 GO_BUILD_CACHE ?= /tmp/dokku-go-build-cache GO_MOD_CACHE ?= /tmp/dokku-go-mod-mod GO_ROOT_MOUNT ?= $$PWD/../..:$(GO_REPO_ROOT) -GOARCH ?= amd64 +ifndef TARGETARCH +TARGETARCH := $(shell dpkg --print-architecture 2>/dev/null || uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/') +endif +GOARCH ?= $(TARGETARCH) .PHONY: build-in-docker build clean src-clean diff --git a/contrib/build-dokku.Dockerfile b/contrib/build-dokku.Dockerfile index 7f08388fa..4a4a80dc8 100644 --- a/contrib/build-dokku.Dockerfile +++ b/contrib/build-dokku.Dockerfile @@ -35,8 +35,8 @@ RUN PLUGIN_MAKE_TARGET=${PLUGIN_MAKE_TARGET} \ DOKKU_GIT_REV=${DOKKU_GIT_REV} \ IS_RELEASE=${IS_RELEASE} \ SKIP_GO_CLEAN=true \ - make version copyfiles \ - && make deb-dokku + GOARCH=amd64 make version copyfiles \ + && DOKKU_ARCHITECTURE=amd64 GOARCH=amd64 make deb-dokku FROM builder AS arm64 diff --git a/contrib/dependencies.json b/contrib/dependencies.json index 2168244f6..f3fdf77af 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -1,11 +1,19 @@ { "dependencies": [ { - "name": "docker-container-healthchecker", - "version": "0.16.0", + "name": "coredns-docker", + "version": "0.8.0", "urls": { - "amd64": "https://hub.diyla.com/https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.0/docker-container-healthchecker-linux-amd64", - "arm64": "https://hub.diyla.com/https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.0/docker-container-healthchecker-linux-arm64" + "amd64": "https://hub.diyla.com/https://github.com/dokku/coredns-docker/releases/download/0.8.0/coredns-docker-linux-amd64", + "arm64": "https://hub.diyla.com/https://github.com/dokku/coredns-docker/releases/download/0.8.0/coredns-docker-linux-arm64" + } + }, + { + "name": "docker-container-healthchecker", + "version": "0.16.1", + "urls": { + "amd64": "https://hub.diyla.com/https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.1/docker-container-healthchecker-linux-amd64", + "arm64": "https://hub.diyla.com/https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.1/docker-container-healthchecker-linux-arm64" } }, { @@ -34,18 +42,18 @@ }, { "name": "pack", - "version": "0.40.9", + "version": "0.40.11", "urls": { - "amd64": "https://hub.diyla.com/https://github.com/buildpacks/pack/releases/download/v0.40.9/pack-v0.40.9-linux.tgz", - "arm64": "https://hub.diyla.com/https://github.com/buildpacks/pack/releases/download/v0.40.9/pack-v0.40.9-linux-arm64.tgz" + "amd64": "https://hub.diyla.com/https://github.com/buildpacks/pack/releases/download/v0.40.11/pack-v0.40.11-linux.tgz", + "arm64": "https://hub.diyla.com/https://github.com/buildpacks/pack/releases/download/v0.40.11/pack-v0.40.11-linux-arm64.tgz" } }, { "name": "procfile-util", - "version": "0.20.8", + "version": "0.20.9", "urls": { - "amd64": "https://hub.diyla.com/https://github.com/dokku/procfile-util/releases/download/v0.20.8/procfile-util-linux-amd64", - "arm64": "https://hub.diyla.com/https://github.com/dokku/procfile-util/releases/download/v0.20.8/procfile-util-linux-arm64" + "amd64": "https://hub.diyla.com/https://github.com/dokku/procfile-util/releases/download/v0.20.9/procfile-util-linux-amd64", + "arm64": "https://hub.diyla.com/https://github.com/dokku/procfile-util/releases/download/v0.20.9/procfile-util-linux-arm64" } }, { @@ -86,18 +94,18 @@ }, { "name": "dokku-update", - "version": "0.10.0", + "version": "0.11.0", "urls": { - "amd64": "https://hub.diyla.com/https://github.com/dokku/dokku-update/releases/download/v0.10.0/dokku-update", - "arm64": "https://hub.diyla.com/https://github.com/dokku/dokku-update/releases/download/v0.10.0/dokku-update" + "amd64": "https://hub.diyla.com/https://github.com/dokku/dokku-update/releases/download/v0.11.0/dokku-update", + "arm64": "https://hub.diyla.com/https://github.com/dokku/dokku-update/releases/download/v0.11.0/dokku-update" } }, { "name": "herokuish", - "version": "0.11.16", + "version": "0.11.25", "urls": { - "amd64": "https://hub.diyla.com/https://github.com/gliderlabs/herokuish/releases/download/v0.11.16/herokuish_0.11.16_linux_x86_64.tgz", - "arm64": "https://hub.diyla.com/https://github.com/gliderlabs/herokuish/releases/download/v0.11.16/herokuish_0.11.16_linux_x86_64.tgz" + "amd64": "https://hub.diyla.com/https://github.com/gliderlabs/herokuish/releases/download/v0.11.25/herokuish_0.11.25_linux_x86_64.tgz", + "arm64": "https://hub.diyla.com/https://github.com/gliderlabs/herokuish/releases/download/v0.11.25/herokuish_0.11.25_linux_x86_64.tgz" } } ] diff --git a/contrib/release-dokku b/contrib/release-dokku index 3f69153e7..92506e154 100755 --- a/contrib/release-dokku +++ b/contrib/release-dokku @@ -144,7 +144,7 @@ fn-publish-package() { fi done elif [[ "$DIST" == "ubuntu" ]]; then - OS_IDS=("jammy" "noble") + OS_IDS=("jammy" "noble" "resolute") for OS_ID in "${OS_IDS[@]}"; do log-info "(release-dokku) pushing ${RELEASE_TYPE} to packagecloud.com/${REPOSITORY}/${DIST}" package_cloud push "${REPOSITORY}/${DIST}/${OS_ID}" "$PACKAGE_NAME" diff --git a/deb.mk b/deb.mk index 528c0177e..d9a32909b 100644 --- a/deb.mk +++ b/deb.mk @@ -1,7 +1,7 @@ BUILD_DIRECTORY ?= /tmp DOKKU_DESCRIPTION = 'Docker powered PaaS that helps you build and manage the lifecycle of applications' DOKKU_REPO_NAME ?= dokku/dokku -DOKKU_ARCHITECTURE ?= amd64 +DOKKU_ARCHITECTURE ?= $(GOARCH) ifndef IS_RELEASE IS_RELEASE = true diff --git a/debian/control b/debian/control index c89e55434..4d5e30a93 100644 --- a/debian/control +++ b/debian/control @@ -1,9 +1,9 @@ Package: dokku -Version: 0.38.27 +Version: 0.38.34 Section: web Priority: optional Architecture: amd64 -Depends: apache2-utils, locales, git, cpio, cron | cron-daemon, curl, logrotate, man-db, netcat, sshcommand, docker-engine-cs (>= 19.03.0) | docker-engine (>= 19.03.0) | docker-io (>= 19.03.0) | docker.io (>= 19.03.0) | docker-ce (>= 19.03.0) | docker-ee (>= 19.03.0) | moby-engine, docker-compose-plugin | moby-compose | docker-compose, docker-buildx-plugin | moby-buildx | docker-buildx, docker-container-healthchecker, docker-image-labeler, lambda-builder, net-tools, netrc, parallel, procfile-util, rsync, dos2unix, unzip, util-linux +Depends: apache2-utils, locales, git, cpio, cron | cron-daemon, curl, logrotate, man-db, netcat, sshcommand, docker-engine-cs (>= 19.03.0) | docker-engine (>= 19.03.0) | docker-io (>= 19.03.0) | docker.io (>= 19.03.0) | docker-ce (>= 19.03.0) | docker-ee (>= 19.03.0) | moby-engine, docker-compose-plugin | moby-compose | docker-compose, docker-buildx-plugin | moby-buildx | docker-buildx, coredns-docker, docker-container-healthchecker, docker-image-labeler, lambda-builder, net-tools, netrc, parallel, procfile-util, rsync, dos2unix, unzip, util-linux Recommends: herokuish, bash-completion, dokku-update, dokku-event-listener Pre-Depends: gliderlabs-sigil, jq, nginx (>= 1.8.0) | openresty, bind9-dnsutils, debconf, plugn, sudo, python3, rsyslog Maintainer: Jose Diaz-Gonzalez diff --git a/debian/postinst b/debian/postinst index 6edbbed79..61efedef1 100755 --- a/debian/postinst +++ b/debian/postinst @@ -109,14 +109,18 @@ setup-plugins() { db_get "dokku/nginx_enable" echo "Enabling all core plugins" find "${DOKKU_LIB_ROOT}/core-plugins/available" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read -r plugin; do + if [ -d "${DOKKU_LIB_ROOT}/plugins/available/$plugin" ]; then + continue + fi + + ln -s "${DOKKU_LIB_ROOT}/core-plugins/available/$plugin" "${DOKKU_LIB_ROOT}/plugins/available/$plugin" if [ "$plugin" = "nginx-vhosts" ] && [ "$RET" = "false" ]; then echo "Skipping enable of nginx-vhosts plugin" continue - elif [ ! -d "${DOKKU_LIB_ROOT}/plugins/available/$plugin" ]; then - ln -s "${DOKKU_LIB_ROOT}/core-plugins/available/$plugin" "${DOKKU_LIB_ROOT}/plugins/available/$plugin" - PLUGIN_PATH="${DOKKU_LIB_ROOT}/core-plugins" plugn enable "$plugin" - PLUGIN_PATH="${DOKKU_LIB_ROOT}/plugins" plugn enable "$plugin" fi + + PLUGIN_PATH="${DOKKU_LIB_ROOT}/core-plugins" plugn enable "$plugin" + PLUGIN_PATH="${DOKKU_LIB_ROOT}/plugins" plugn enable "$plugin" done find -L "${DOKKU_LIB_ROOT}/core-plugins" -type l -delete find -L "${DOKKU_LIB_ROOT}/plugins" -type l -delete diff --git a/docker/etc/runit/runsvdir/default/coredns-docker/run b/docker/etc/runit/runsvdir/default/coredns-docker/run new file mode 100755 index 000000000..312b939d2 --- /dev/null +++ b/docker/etc/runit/runsvdir/default/coredns-docker/run @@ -0,0 +1,7 @@ +#!/bin/bash + +exec >>/var/log/services/coredns-docker +exec 2>&1 + +echo "Running coredns-docker" +exec /usr/bin/coredns-docker -conf /etc/coredns/Corefile diff --git a/docs/README.md b/docs/README.md index 00e8acd4a..8178c6227 100644 --- a/docs/README.md +++ b/docs/README.md @@ -97,6 +97,7 @@ This documentation covers the installation, configuration, and usage of Dokku - - [Registry Management](advanced-usage/registry-management.md) - Docker registry integration - [Repository Management](advanced-usage/repository-management.md) - Git repository settings - [Build Tracking](advanced-usage/builds.md) - Inspect, cancel, and manage builds +- [Datastore Management](advanced-usage/datastore-management.md) - Create and link datastore services - [Deployment Tasks](advanced-usage/deployment-tasks.md) - Pre/post deploy hooks - [Event Logs](advanced-usage/event-logs.md) - Dokku event history - [Backup and Recovery](advanced-usage/backup-recovery.md) - Data backup strategies @@ -122,6 +123,7 @@ This documentation covers the installation, configuration, and usage of Dokku - ### Migration Guides +- [0.39.0](appendices/0.39.0-migration-guide.md) - [0.38.0](appendices/0.38.0-migration-guide.md) - [0.37.0](appendices/0.37.0-migration-guide.md) - [0.36.0](appendices/0.36.0-migration-guide.md) diff --git a/docs/_build/Dockerfile b/docs/_build/Dockerfile index 00066b5cb..30eb3b105 100644 --- a/docs/_build/Dockerfile +++ b/docs/_build/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.15.0rc1-alpine +FROM python:3.16.0a1-alpine WORKDIR /usr/src/app diff --git a/docs/_build/requirements.txt b/docs/_build/requirements.txt index e69d4ca7c..40640ac44 100644 --- a/docs/_build/requirements.txt +++ b/docs/_build/requirements.txt @@ -1,23 +1,23 @@ beautifulsoup4==4.15.0 -click==8.4.2 +click==8.5.1 ghp-import==2.1.0 -importlib-metadata==9.0.0 +importlib-metadata==10.2.0 Jinja2==3.1.6 -Markdown>=3.10.3,<3.11 -MarkupSafe==3.0.3 +Markdown>=3.11.3,<3.12 +MarkupSafe==3.0.4 mergedeep==1.3.4 mkdocs==1.6.1 mkdocs-exclude==1.0.2 -mkdocs-material==9.7.7 +mkdocs-material==9.8.1 mkdocs-material-extensions==1.3.1 packaging==26.3 Pygments==2.21.0 -pymdown-extensions==11.0.1 -pyparsing==3.3.2 -python-dateutil==2.9.0.post0 +pymdown-extensions==13.0 +pyparsing==3.3.3 +python-dateutil==4.0.0 PyYAML==6.0.3 pyyaml_env_tag==1.1 six==1.17.0 -soupsieve==2.9.2 -watchdog==6.0.0 -zipp==4.1.0 +soupsieve==4.1 +watchdog==7.1.0 +zipp==4.1.2 diff --git a/docs/advanced-usage/builds.md b/docs/advanced-usage/builds.md index 1e80bca43..d94bb51fb 100644 --- a/docs/advanced-usage/builds.md +++ b/docs/advanced-usage/builds.md @@ -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-` so `journalctl -t dokku-` 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 | diff --git a/docs/advanced-usage/datastore-management.md b/docs/advanced-usage/datastore-management.md new file mode 100644 index 000000000..4c27f4d85 --- /dev/null +++ b/docs/advanced-usage/datastore-management.md @@ -0,0 +1,127 @@ +# Datastore Management + +``` +datastore:app-links [] # list all datastore service links for a given app +datastore:backup [--id ]... # create a backup of the datastore service to an existing s3 bucket +datastore:backup-auth ... # set up authentication for backups on the datastore service +datastore:backup-deauth [--id ] # remove backup authentication for the datastore service +datastore:backup-logs [-t|--tail []] # print the most recent output of the scheduled backups of the service +datastore:backup-schedule ... # schedule a backup of the datastore service +datastore:backup-schedule-cat [--id ] # cat the crontab line of a scheduled backup for the service +datastore:backup-set-encryption [--id ] # set encryption for all future backups of datastore service +datastore:backup-set-public-key-encryption [--id ] # set GPG Public Key encryption for all future backups of datastore service +datastore:backup-unschedule [--id |--all] # unschedule the backup of the datastore service +datastore:backup-unset-encryption [--id ] # unset encryption for future backups of the datastore service +datastore:backup-unset-public-key-encryption [--id ] # unset GPG Public Key encryption for future backups of the datastore service +datastore:clone [--clone-flags...] # create container then copy data from into +datastore:connect # connect to the service via the connection tool +datastore:create [--create-flags...] # create a datastore service +datastore:create-database # create a database in the datastore service +datastore:create-user [] # create a user in the datastore service +datastore:destroy [-f|--force] # delete the datastore service/data/container if there are no links left +datastore:destroy-database [-f|--force] # destroy a database created in the datastore service +datastore:destroy-user [-f|--force] # destroy a user created in the datastore service +datastore:enter # enter or run a command in a running datastore service container +datastore:exists # check if the datastore service exists +datastore:export [-f|--file ] [--force] # export a dump of the datastore service database +datastore:expose # expose a datastore service on custom host:port if provided (random port on the 0.0.0.0 interface if otherwise unspecified) +datastore:help [ []] # print the help for every datastore, one datastore or one of its commands +datastore:import [-f|--file ] # import a dump into the datastore service database +datastore:info [] [--info-flags...] # print the service information +datastore:label ... # add docker labels to the service container +datastore:link [] [--link-flags...] # link the datastore service to the app +datastore:linked [] # check if the datastore service is linked to an app +datastore:links # list all apps linked to the datastore service +datastore:list # list all datastore services +datastore:logs [-t|--tail []] # print the most recent log(s) for this service +datastore:mount [--replace] ... # mount a host path or docker volume into the service container +datastore:pause # pause a running datastore service +datastore:promote [] # promote service as DATABASE_URL in +datastore:reexpose # reexpose a datastore service, applying its expose settings +datastore:reset [-f|--force] # delete all data in the datastore service, keeping the service and its links +datastore:restart # graceful shutdown and restart of the datastore service container +datastore:set []... # set or clear a property for a service +datastore:set-user-password []... # change the password of a user created in the datastore service +datastore:start # start a previously stopped datastore service +datastore:stop # stop a running datastore service +datastore:unexpose # unexpose a previously exposed datastore service +datastore:unlabel [--all] [...] # remove one or all docker labels from the service container +datastore:unlink [] [-n|--no-restart] # unlink the datastore service from the app +datastore:unmount [--all] [...] # remove one or all mounts from the service container +datastore:upgrade [--upgrade-flags...] # upgrade 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. + +```shell +dokku datastore:create postgres lollipop +dokku datastore:link postgres lollipop node-js-app +``` + +`datastore:unlink` removes the link, and `datastore:destroy` removes a service once nothing is linked to it. + +```shell +dokku datastore:unlink postgres lollipop node-js-app +dokku datastore:destroy postgres lollipop +``` + +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. + +```shell +dokku datastore:backup-auth postgres lollipop AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY +dokku datastore:backup-schedule postgres lollipop "0 3 * * *" my-s3-bucket +``` + +### Images + +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: ` to run `dokku datastore: postgres `. + +To move back, install the official plugin again from a release running at least the version `dokku datastore:version` prints. diff --git a/docs/advanced-usage/deployment-tasks.md b/docs/advanced-usage/deployment-tasks.md index 1c278a803..41e3cf372 100644 --- a/docs/advanced-usage/deployment-tasks.md +++ b/docs/advanced-usage/deployment-tasks.md @@ -67,7 +67,7 @@ Sometimes it may be desirable to set a different path for a given app, e.g. when dokku app-json:set node-js-app appjson-path .dokku/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 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. diff --git a/docs/advanced-usage/docker-options.md b/docs/advanced-usage/docker-options.md index 6469f3878..d938fed73 100644 --- a/docs/advanced-usage/docker-options.md +++ b/docs/advanced-usage/docker-options.md @@ -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. @@ -288,7 +288,7 @@ dokku docker-options:list node-js-app --phase deploy 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_` 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 | diff --git a/docs/advanced-usage/persistent-storage.md b/docs/advanced-usage/persistent-storage.md index 6943a9b48..8926b34a9 100644 --- a/docs/advanced-usage/persistent-storage.md +++ b/docs/advanced-usage/persistent-storage.md @@ -6,24 +6,23 @@ The preferred method to attach persistent storage to a Dokku-managed container is the Dokku storage plugin. ``` -storage:annotations:report [] [] # Display annotations for one or more storage entries -storage:annotations:set [] # Set or clear a single annotation on a storage entry -storage:create [] [flags] # Register a named storage entry -storage:destroy [--force] [--destroy-host-dir] # Remove a named storage entry (must be unmounted from every app first) -storage:ensure-directory [--chown option] # [DEPRECATED] use storage:create instead -storage:exec [-- ...] # Run a command (or shell) in a temporary container that mounts the entry -storage:info [--format text|json] # Show details for one storage entry -storage:labels:report [] [] # Display labels for one or more storage entries -storage:labels:set [] # Set or clear a single label on a storage entry -storage:list [--format text|json] # List bind mounts for an app's container(s) (legacy host:container view) -storage:list-entries [--scheduler s] [--format text|json] # List registered storage entries -storage:mount --container-dir [flags] # Mount a named entry into an app -storage:mount # [LEGACY] colon-form mount, docker-local only -storage:report [] [] # Display a storage report for one or more apps -storage:report --global # Display a cluster-wide entry inventory -storage:set [] # Update a storage entry in place -storage:unmount [--container-dir ] # Remove an attachment -storage:wait # Block until a k3s entry's PVC is bound +storage:annotations:report [] [] # Displays annotations for one or more storage entries +storage:annotations:set [] # Set or clear a single annotation on a storage entry +storage:create [] [flags] # Register a named storage entry +storage:destroy [--force] [--destroy-host-dir] # Remove a named storage entry (must be unmounted from every app first) +storage:ensure-directory [--chown option] # [DEPRECATED] use storage:create instead +storage:exec [-- ...] # Run a command (or shell) in a temporary container that mounts the entry +storage:info [--format text|json] # Show details for one storage entry +storage:labels:report [] [] # Displays labels for one or more storage entries +storage:labels:set [] # Set or clear a single label on a storage entry +storage:list [--format text|json] # List bind mounts for an app's container(s) (legacy host:container view) +storage:list-entries [--scheduler s] [--format text|json] # List registered storage entries +storage:migrate [|--all] # Re-run the legacy -v to attachment migration for an app +storage:mount [--replace] ... [flags] # Create or replace bind mounts +storage:report [|--global] [] # Displays a storage report for one or more apps +storage:set [] # Update a storage entry in place +storage:unmount [--all] [...] [flags] # Remove one or all bind mounts +storage:wait # 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`. + ```shell dokku storage:ensure-directory node-js-app ``` @@ -171,7 +172,7 @@ dokku storage:set node-js-data chown The following properties can be set: | Property | Description | Unsetting it means | -|---|---|---| +| --- | --- | --- | | `chown` | Ownership preset or numeric uid for the host directory | no chown is performed | | `mode` | Octal permissions for the host directory | permissions are left alone | | `namespace` | Namespace holding the PVC (k3s) | the `default` namespace | @@ -248,7 +249,7 @@ dokku storage:mount node-js-app /var/lib/dokku/data/storage/node-js-app:/app/sto dokku storage:mount node-js-app some-docker-volume:/app/storage ``` -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: + +```shell +dokku storage:create node-js-scratch +dokku storage:mount node-js-app node-js-scratch --container-dir /scratch --process-type worker +``` + +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: + +```shell +dokku storage:create node-js-data +dokku storage:create node-js-cache + +dokku storage:mount node-js-app node-js-data --container-dir /app/storage +# fails: the default-scoped mount above also applies to web +dokku storage:mount node-js-app node-js-cache --container-dir /app/storage --process-type web +``` + +Two *named* scopes never apply to the same container, so they may each bind a different entry at the same container path: + +```shell +dokku storage:mount node-js-app node-js-data --container-dir /app/storage --process-type web +dokku storage:mount node-js-app node-js-cache --container-dir /app/storage --process-type worker +``` + +#### Replacing the entire mount set + +> [!IMPORTANT] +> 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. + +```shell +dokku storage:mount --replace node-js-app node-js-data:/app/storage node-js-cache:/cache +``` + +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: + +```shell +dokku storage:mount --replace node-js-app \ + node-js-data:/app/storage:ro,Z,volume-subpath=uploads,volume-chown=herokuish \ + node-js-cache:/cache:phase=deploy,phase=run,noexec +``` + +| Token | Meaning | +| --- | --- | +| `ro` | Mount the volume read-only | +| `rw` | Mount it read-write, the explicit opposite of `ro` and the default | +| `volume-subpath=` | Subpath within the entry | +| `volume-chown=