diff --git a/.codacy.yml b/.codacy.yml deleted file mode 100644 index bd3ed9e8bbe..00000000000 --- a/.codacy.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -engines: - duplication: - exclude_paths: - - "*" -exclude_paths: - - vendor/**/* - - tests/**/* - - "**_test.go" - - contrib/* diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9f09bd79f66..209e713df36 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -2,13 +2,16 @@ FROM dokku/dokku:latest RUN apt-get update RUN apt-get install --no-install-recommends -y build-essential file nano && \ - apt-get install --no-install-recommends -y help2man shellcheck uuid-runtime wget xmlstarlet && \ + apt-get install --no-install-recommends -y software-properties-common help2man shellcheck uuid-runtime wget xmlstarlet && \ apt-get clean autoclean && \ apt-get autoremove --yes && \ rm -rf /var/lib/apt/lists/* -RUN wget https://dl.google.com/go/go1.16.10.linux-amd64.tar.gz && \ - tar -xvf go1.16.10.linux-amd64.tar.gz && \ +ARG TARGETARCH +COPY common.mk common.mk +RUN GOLANG_VERSION=$(grep BUILD_IMAGE common.mk | cut -d' ' -f3 | cut -d':' -f2) && \ + wget https://dl.google.com/go/go${GOLANG_VERSION}.linux-${TARGETARCH}.tar.gz && \ + tar -xvf go${GOLANG_VERSION}.linux-${TARGETARCH}.tar.gz && \ mv go /usr/local RUN GOROOT=/usr/local/go /usr/local/go/bin/go install golang.org/x/tools/gopls@latest 2>&1 diff --git a/.devcontainer/bin/bump-modules b/.devcontainer/bin/bump-modules new file mode 100755 index 00000000000..88e815bd1a8 --- /dev/null +++ b/.devcontainer/bin/bump-modules @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +export GOWORK=off +pushd /root/go/src/github.com/dokku/dokku/plugins/ >/dev/null || exit 1 +for plugin in *; do + if [[ ! -f "/root/go/src/github.com/dokku/dokku/plugins/$plugin/go.mod" ]]; then continue; fi + echo "$plugin" + pushd /root/go/src/github.com/dokku/dokku/plugins/$plugin || exit 1 + echo "running go mod tidy for $plugin" + if ! go mod tidy; then + echo "go mod tidy failed for $plugin" + exit 1 + fi + echo "running go mod download for $plugin" + if ! go mod download; then + echo "go mod download failed for $plugin" + exit 1 + fi + popd >/dev/null || exit 1 +done +popd >/dev/null || exit 1 diff --git a/.devcontainer/bin/deploy-test-app b/.devcontainer/bin/deploy-test-app new file mode 100755 index 00000000000..2a1b2ab79d5 --- /dev/null +++ b/.devcontainer/bin/deploy-test-app @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +main() { + declare APP="${1:-test}" + + if ! dokku apps:exists $APP; then + dokku apps:create $APP + fi + + dokku builder-herokuish:set $APP allowed true + dokku git:sync --build $APP https://github.com/dokku/smoke-test-app.git +} + +main "$@" diff --git a/.devcontainer/bin/setup-dev-env b/.devcontainer/bin/setup-dev-env new file mode 100755 index 00000000000..066ac45d36b --- /dev/null +++ b/.devcontainer/bin/setup-dev-env @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +install-go-devtools() { + go install github.com/go-delve/delve/cmd/dlv@latest + go install honnef.co/go/tools/cmd/staticcheck@latest + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +} + +setup-ci() { + local dokku_root="/root/go/src/github.com/dokku/dokku" + pushd "$plugin_root" >/dev/null || true + make ci-dependencies setup-deploy-tests + popd >/dev/null || true +} + +main() { + install-go-devtools + setup-ci +} + +main "$@" diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b1112f0bee3..f0a2a782567 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,22 +4,37 @@ "context": ".." }, "containerEnv": { + "DOKKU_HOSTNAME": "dokku.me", "DOKKU_HOST_ROOT": "${localWorkspaceFolder}/tmp/data/home/dokku", + "DOKKU_LIB_HOST_ROOT": "${localWorkspaceFolder}/tmp/data/var/lib/dokku", "GO_ROOT_MOUNT": "${localWorkspaceFolder}:/go/src/github.com/dokku/dokku" }, - "extensions": [ - "golang.go", - "mads-hartmann.bash-ide-vscode", - "ms-vscode.makefile-tools" + "customizations": { + "vscode": { + "extensions": [ + "golang.go", + "mads-hartmann.bash-ide-vscode", + "ms-vscode.makefile-tools", + "sleistner.vscode-fileutils", + "ms-azuretools.vscode-docker" + ] + } + }, + "initializeCommand": [ + "mkdir", + "-p", + "tmp/data" ], - "initializeCommand": ["mkdir", "-p", "tmp/data"], "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind", "source=${localWorkspaceFolder}/tmp/data/,target=/mnt/dokku/,type=bind" ], "overrideCommand": false, - "postCreateCommand": "make setup-deploy-tests", - "runArgs": ["--init"], + "postCreateCommand": "setup-dev-env", + "postStartCommand": "printenv > .vscode/devcontainer.env", + "runArgs": [ + "--init" + ], "workspaceFolder": "/root/go/src/github.com/dokku/dokku", "workspaceMount": "type=bind,source=${localWorkspaceFolder},target=/root/go/src/github.com/dokku/dokku,consistency=cached" -} +} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore index a06e41fd677..b3b351f2067 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,8 +6,13 @@ contrib build docs tests -!build/dokku.deb +!build/package/* +!docs/_build/entrypoint +!docs/_build/hooks.py +!docs/_build/requirements.txt !tests/dhparam.pem !contrib/bash-completion +!contrib/dependencies.json !contrib/docker +!contrib/update-deb-dependencies vendor diff --git a/.editorconfig b/.editorconfig index b4d8d412f90..dddd5ce123f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -19,3 +19,6 @@ indent_size = 4 insert_final_newline = true indent_style = tab indent_size = 4 + +[*.md] +tab_width = 4 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 00000000000..d4029556501 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,67 @@ +--- +name: Bug Report +description: File a bug report +labels: ["needs: investigation"] +type: Bug +body: + - type: markdown + attributes: + value: | + Consider [sponsoring Dokku](https://github.com/sponsors/dokku). Sponsorship goes directly to supporting activities such as fixing bugs and general maintenance. + - type: textarea + id: description + attributes: + label: Description of problem + description: What happened? What did you expect to happen? + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: What are the steps that we need to follow to reproduce this issue? + validations: + required: true + - type: textarea + id: report-output + attributes: + label: dokku report $APP_NAME + description: Please paste the output of the command `dokku report $APP_NAME`, where `$APP_NAME` is the name of your app + validations: + required: true + - type: textarea + id: additional-output + attributes: + label: Additional information + description: | + This information is useful for debugging app issues: + + - Container Inspect Output (if applicable) via `dokku ps:inspect APP_NAME` + - The nginx configuration (if applicable) via `dokku nginx:show-config APP_NAME` + - Link to the exact repository being deployed (if possible/applicable) + + If a deploy is failing or behaving unexpectedly: + + - Application name + - The type of application being deployed (node, php, python, ruby, etc.) + - If using buildpacks, which custom buildpacks are in use + - If using a Dockerfile, the contents of that file + - If it exists, the contents of your Procfile. + validations: + required: false + - type: textarea + id: logs + attributes: + label: "Output of failing commands after running: dokku trace:off" + render: shell + validations: + required: false + - type: textarea + id: trace-logs + attributes: + label: "Output of failing commands after running: dokku trace:on" + description: | + **BEWARE**: `dokku trace:on` will cause some commands to print environment variables, be sure you're not exposing any sensitive information when posting issues. You may replace these values with XXXXXX + render: shell + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/buildpack_issue.yaml b/.github/ISSUE_TEMPLATE/buildpack_issue.yaml new file mode 100644 index 00000000000..4dbb47491f1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/buildpack_issue.yaml @@ -0,0 +1,71 @@ +--- +name: Buildpack Issue +description: Is your deploy failing while building your application using buildpacks? Check this out. +labels: ["upstream: buildpacks"] +body: + - type: markdown + attributes: + value: | + If your build is stuck downloading something, compiling a file, or seems to always fail around the same area, Github Issues is not a great place to debug. This is because these sorts of issues tend to take a lot of back and forth to figure out, and the code for buildpacks is not generally maintained by the Dokku project. + +
+ Debugging tips: + + - If you see text that says `Killed` in your output, attempt to increase free memory by using a larger server or [add swap memory](https://dokku.com/docs/getting-started/troubleshooting/#build-fails-with-killed-message). + - Turn on buildpack trace mode via `dokku config:set $APP_NAME BUILDPACK_XTRACE=1` and retry your deploy. Not all buildpacks support this, but many will output extra debugging information. + - Your hosting provider may have issues accessing AWS S3. Verify a slow/stalled download works by directly curling the file from your host. + - Many buildpacks allow downloading a file from an alternative location specified via environment variable. + - [Increase curl timeouts](https://dokku.com/docs/getting-started/troubleshooting/#deployment-fails-because-of-slow-internet-connection-messages-s) if you see gzip download issues. + - If using multi-buildpack support but a buildpack is failing to apply, ensure you have any files necessary for the `bin/detect` script of the buildpack to `exit 0`. Selected buildpacks must still pass the detection phase in order to be used. +
+ + If you are still having issues, file a ticket here and then further discussing the ticket in one of our [Monitored Support Channels](https://dokku.com/docs/getting-started/where-to-get-help/#monitored-locations) (Discord, Github Discussions, Slack). Your filed ticket may be closed or locked, but any subsequent discovery will be posted to the ticket for posterity. + - type: textarea + id: description + attributes: + label: Description of problem + description: What happened? What did you expect to happen? + validations: + required: true + - type: textarea + id: report-output + attributes: + label: dokku report $APP_NAME + description: Please paste the output of the command `dokku report $APP_NAME`, where `$APP_NAME` is the name of your app. + validations: + required: true + - type: textarea + id: additional-output + attributes: + label: Additional information + description: | + This information is useful for debugging app issues: + + - Container Inspect Output (if applicable) via `dokku ps:inspect APP_NAME` + - The nginx configuration (if applicable) via `dokku nginx:show-config APP_NAME` + - Link to the exact repository being deployed (if possible/applicable) + + If a deploy is failing or behaving unexpectedly: + + - Application name + - The type of application being deployed (node, php, python, ruby, etc.) + - If using buildpacks, which custom buildpacks are in use + - If it exists, the contents of your Procfile. + validations: + required: true + - type: textarea + id: logs + attributes: + label: "Output of failing deploy after running: dokku trace:off" + render: shell + validations: + required: true + - type: textarea + id: trace-logs + attributes: + label: "Output of failing deploy after running: dokku trace:on" + description: | + **BEWARE**: `dokku trace:on` will cause some commands to print environment variables, be sure you're not exposing any sensitive information when posting issues. You may replace these values with XXXXXX + render: shell + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/documentation.yaml b/.github/ISSUE_TEMPLATE/documentation.yaml new file mode 100644 index 00000000000..f7cfaeafca2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.yaml @@ -0,0 +1,26 @@ +--- +name: Documentation Issue +description: Let us know that something in the docs needs clarification +labels: ["needs: investigation", "type: documentation"] +body: + - type: markdown + attributes: + value: | + Consider [sponsoring Dokku](https://github.com/sponsors/dokku). Sponsorship goes directly to supporting activities such as fixing bugs and general maintenance. + - type: input + id: docs-link + attributes: + label: Link to the documentation page + description: Which docs page is impacted? + - type: textarea + id: description + attributes: + label: Documentation problem + description: Describe what you think is incorrect, not well documented, or missing from the documentation + validations: + required: true + - type: textarea + id: report-output + attributes: + label: dokku report + description: Please paste the output of the command `dokku report` so we know what systems any issue might apply to diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 00000000000..3bf12ccde56 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,18 @@ +--- +name: Feature Request +description: Have an idea for a new feature? Ask away! +labels: ["needs: plan", "type: enhancement"] +body: + - type: markdown + attributes: + value: | + While pull requests are always welcome, feel free to fill this out with an idea of what you would like to happen and we can discuss if and how it should be implemented in Dokku. + + Consider [sponsoring Dokku](https://github.com/sponsors/dokku). Sponsorship goes directly to supporting activities such as implementing new features and upgrading existing functionality. + - type: textarea + id: description + attributes: + label: Description of feature + description: When you perform what command or action, what do you think should happen? + validations: + required: true diff --git a/.github/actions/build-image/action.yml b/.github/actions/build-image/action.yml new file mode 100644 index 00000000000..2a9de383f59 --- /dev/null +++ b/.github/actions/build-image/action.yml @@ -0,0 +1,37 @@ +--- +name: 'build-image' + +description: 'builds a dokku docker image' + +inputs: + architecture: + description: 'platform to build' + required: true + default: 'linux/amd64' + +runs: + using: "composite" + steps: + - name: download packages + uses: actions/download-artifact@v8 + with: + name: build + path: build + + - name: set up docker buildx + id: buildx + uses: docker/setup-buildx-action@v4 + + - name: build docker image + shell: bash + run: | + docker buildx build \ + --load \ + --progress plain \ + --tag "dokku/dokku:latest" \ + . + + - name: smoke test image + shell: bash + run: | + make test-image-healthcheck IMAGE_TAG=dokku/dokku:latest diff --git a/.github/commands/bump-azure b/.github/commands/bump-azure index a40c381b0f4..fbb0b27b5af 100755 --- a/.github/commands/bump-azure +++ b/.github/commands/bump-azure @@ -18,7 +18,11 @@ main() { cd tmp - $GH_FOLDER/bin/gh auth login --with-token <<<"$BOT_GITHUB_API_TOKEN" + if [[ -n "$BOT_GITHUB_API_TOKEN" ]]; then + $GH_FOLDER/bin/gh auth login --with-token <<<"$BOT_GITHUB_API_TOKEN" + else + echo "BOT_GITHUB_API_TOKEN is not set, skipping gh auth login" + fi echo "=====> Cloning quickstart templates" rm -rf azure-quickstart-templates >/dev/null @@ -35,15 +39,29 @@ main() { contents="$(jq --arg VERSION "$VERSION" '.parameters.dokkuVersion.defaultValue = $VERSION' application-workloads/dokku/dokku-vm/azuredeploy.json)" echo "${contents}" >application-workloads/dokku/dokku-vm/azuredeploy.json + # update the version in main.bicep + # the line being updated looks like this: + # param dokkuVersion string = '0.28.4' + # we need to update the value of dokkuVersion to the new version + sed -i "s/param dokkuVersion string = '[^']*'/param dokkuVersion string = '$VERSION'/" application-workloads/dokku/dokku-vm/main.bicep + echo "=====> Updating remote repository" git add application-workloads/dokku/dokku-vm git checkout -b dokku-$VERSION git commit -m "Update dokku-vm dokku version to $VERSION" - git remote set-url origin "https://$BOT_GITHUB_USERNAME:$BOT_GITHUB_API_TOKEN@github.com/dokku/azure-quickstart-templates.git" - git push -f origin dokku-$VERSION + if [[ -z "$BOT_GITHUB_API_TOKEN" ]] || [[ -z "$BOT_GITHUB_USERNAME" ]]; then + echo "BOT_GITHUB_API_TOKEN or BOT_GITHUB_USERNAME is not set, skipping git remote set-url" + else + git remote set-url origin "https://$BOT_GITHUB_USERNAME:$BOT_GITHUB_API_TOKEN@github.com/dokku/azure-quickstart-templates.git" + git push -f origin dokku-$VERSION + fi - echo "=====> Creating upstream pull request" - ../$GH_FOLDER/bin/gh pr create --head dokku:dokku-$VERSION --repo Azure/azure-quickstart-templates --title "Update dokku-vm dokku version to $VERSION" --body '' + if [[ -z "$BOT_GITHUB_API_TOKEN" ]] || [[ -z "$BOT_GITHUB_USERNAME" ]]; then + echo "BOT_GITHUB_API_TOKEN or BOT_GITHUB_USERNAME is not set, skipping gh pr create" + else + echo "=====> Creating upstream pull request" + ../$GH_FOLDER/bin/gh pr create --head dokku:dokku-$VERSION --repo Azure/azure-quickstart-templates --title "Update dokku-vm dokku version to $VERSION" --body '' + fi popd &>/dev/null } diff --git a/.github/commands/ci-setup b/.github/commands/ci-setup index 43c3ac6f6d8..a6e0cd77c17 100755 --- a/.github/commands/ci-setup +++ b/.github/commands/ci-setup @@ -1,20 +1,24 @@ #!/usr/bin/env bash set -eo pipefail -set -x + +DOKKU_DOMAIN=${DOKKU_DOMAIN:="dokku.me"} echo "=====> set hostname" hostname internal_ip="$(ifconfig eth0 | grep 'inet ' | awk '{print $2}')" -sudo hostname "dokku.me" -echo "dokku.me" | sudo tee /etc/hostname +echo "CI_SERVER_IP=${internal_ip}" >>"$GITHUB_ENV" +sudo hostname "$DOKKU_DOMAIN" +echo "$DOKKU_DOMAIN" | sudo tee /etc/hostname hostname sudo cat /etc/hostname -echo "=====> resolve dokku.me" +echo "=====> resolve $DOKKU_DOMAIN" sudo apt -qq -y --no-install-recommends install net-tools -# dokku.me now resolves to 10.0.0.2. add 10.0.0.2/24 to eth0 -ifconfig -sudo ip addr add 10.0.0.2/24 broadcast 10.0.0.255 dev eth0 +if [[ "$DOKKU_DOMAIN" == "dokku.me" ]]; then + # dokku.me now resolves to 10.0.0.2. add 10.0.0.2/24 to eth0 + ifconfig + sudo ip addr add 10.0.0.2/24 broadcast 10.0.0.255 dev eth0 +fi echo "=====> install ci-dependencies" make ci-dependencies diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a8f9702ae0b..fa2343c691a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,107 +1,449 @@ --- version: 2 updates: + - package-ecosystem: pip + directory: "/docs/_build" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" + - package-ecosystem: gomod + directory: "/plugins/app-json" + 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 + labels: + - "type: dependencies" + - package-ecosystem: "docker" + directory: "/plugins/caddy-vhosts" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" + - package-ecosystem: "docker" + directory: "/plugins/haproxy-vhosts" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" + - package-ecosystem: "docker" + directory: "/plugins/logs" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" + - package-ecosystem: "docker" + directory: "/plugins/openresty-vhosts" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" + - package-ecosystem: "docker" + directory: "/plugins/traefik-vhosts" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" - package-ecosystem: bundler directory: "/tests/apps/ruby" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: bundler directory: "/tests/apps/multi" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/checks-root" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/config" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm - directory: "/tests/apps/dockerfile-dokku-scale" + directory: "/tests/apps/dockerfile-app-json-formations" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/dockerfile-noexpose" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/dockerfile-procfile-bad" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/dockerfile-procfile" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/dockerfile" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/gitsubmodules" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: gomod directory: "/tests/apps/gogrpc" schedule: interval: daily open-pull-requests-limit: 10 - ignore: - - dependency-name: google.golang.org/grpc - versions: - - "> 1.29.1" + labels: + - "type: tests" - package-ecosystem: pip directory: "/tests/apps/multi" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: maven directory: "/tests/apps/java" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: npm + directory: "/tests/apps/multi" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/nodejs-express" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/nodejs-worker" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/nodejs-express-noappjson" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: npm directory: "/tests/apps/nodejs-express-noprocfile" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: composer directory: "/tests/apps/php" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: tests" - package-ecosystem: pip directory: "/tests/apps/python-flask" schedule: interval: daily open-pull-requests-limit: 10 - - package-ecosystem: npm - directory: "/tests/apps/.websocket.disabled" + labels: + - "type: tests" + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-minor" + - "version-update:semver-patch" + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" + - package-ecosystem: "docker" + directory: "/tests/apps/dockerfile-noexpose" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/dockerfile" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/dockerfile-procfile" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/dockerfile-entrypoint" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/dockerfile-procfile-bad" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/go-fail-predeploy" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/zombies-dockerfile-no-tini" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/dockerfile-release" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: pip + directory: "/tests/apps/dockerfile-release" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/dockerfile-app-json-formations" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/gogrpc" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/zombies-dockerfile-tini" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/tests/apps/go-fail-postdeploy" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: tests" + - package-ecosystem: "docker" + directory: "/docs/_build" + schedule: + interval: daily + open-pull-requests-limit: 10 + labels: + - "type: dependencies" + - package-ecosystem: "docker" + directory: "/.devcontainer" schedule: interval: daily open-pull-requests-limit: 10 + labels: + - "type: dependencies" diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index 2b7650bb297..e16596c0a06 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -8,3 +8,30 @@ MD013: false # Inline HTML # https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md033 MD033: false + +# List indentation +# 2 spaces breaks list formatting in mkdocs +# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md007 +MD007: + indent: 4 + +# Fenced code blocks should have a language specified +# We use a second, un-languaged code block to denote the output +# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md040 +MD040: false + +# Blank line inside blockquote +# This is typically done when a section has a "New as of" or "Warning" in addition to a note +# May wish to take advantage of github-style admonitions +# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md028 +MD028: false + +# No duplicate headers +# HISTORY.md has a ton of these +# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md024 +MD024: false + +# First line h1 +# The issue template doesn't have one +# https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md041 +MD041: false diff --git a/.github/linters/.yamllint.yml b/.github/linters/.yamllint.yml index 75da2b7021e..fe6352dcc1f 100644 --- a/.github/linters/.yamllint.yml +++ b/.github/linters/.yamllint.yml @@ -3,3 +3,6 @@ extends: default rules: line-length: disable + +ignore: + - plugins/scheduler-k3s/templates/* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7d59cdbc72..44720039c1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,49 +10,120 @@ on: branches: - 'master' +concurrency: + group: build-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + INSTALL_DOCKER_REPO: 1 + jobs: + check-commit: + runs-on: ubuntu-24.04 + outputs: + skip: ${{ steps.check-commit.outputs.skip }} + + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: check if message contain keywords ace scopeid + id: check-commit + run: | + # checking for "docs" or "[ci skip]" messages + message=$(git log -1 --pretty=format:'%s') + if [[ "$message" =~ "\[(ci skip)\]" ]]; then + echo "Skipping tests due to [ci skip] message" + echo "skip=true" >> $GITHUB_OUTPUT + elif [[ "$message" =~ "^docs:" ]]; then + echo "Skipping tests due to "docs:" prefix on message" + echo "skip=true" >> $GITHUB_OUTPUT + fi + build: name: build - runs-on: ubuntu-18.04 + runs-on: ubuntu-24.04 + needs: check-commit + if: ${{ needs.check-commit.outputs.skip != 'true' }} outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} strategy: fail-fast: true + timeout-minutes: 45 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 + + - name: set up qemu + uses: docker/setup-qemu-action@v4 + + - name: set up docker buildx + id: buildx + uses: docker/setup-buildx-action@v4 - name: build package run: ./tests/ci/setup.sh build + env: + SKIP_IMAGE_BUILD: true - name: set matrix for build id: set-matrix run: | - json=$(python .github/commands/matrix) + json=$(python3 .github/commands/matrix) echo $json - echo "::set-output name=matrix::$(echo "$json")" + echo "matrix=$json" >> $GITHUB_OUTPUT - name: upload packages - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v7 with: name: build path: build + build-image-amd64: + name: build-image.linux/amd64 + needs: build + runs-on: ubuntu-24.04 + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v7 + + - name: build-image + uses: ./.github/actions/build-image + with: + architecture: linux/amd64 + + build-image-arm64: + name: build-image.linux/arm64 + needs: build + runs-on: ubuntu-24.04-arm + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v7 + + - name: build-image + uses: ./.github/actions/build-image + with: + architecture: linux/arm64 + unit-tests: name: unit.${{ matrix.index }} needs: build - runs-on: ubuntu-18.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: ${{fromJson(needs.build.outputs.matrix)}} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: download packages - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v8 with: name: build + path: build - name: ci-setup run: ./.github/commands/ci-setup @@ -61,7 +132,7 @@ jobs: # uses: luchihoratiu/debug-via-ssh@main # with: # NGROK_AUTH_TOKEN: ${{ secrets.NGROK_AUTH_TOKEN }} - # SSH_PASS: ${{ secrets.SSH_PASS }} + # SSH_PASS: ${{ secrets.NGROK_SSH_PASS }} - name: run ci timeout-minutes: 30 @@ -72,7 +143,7 @@ jobs: SYNC_GITHUB_PASSWORD: ${{ secrets.SYNC_GITHUB_PASSWORD }} SYNC_GITHUB_USERNAME: ${{ secrets.SYNC_GITHUB_USERNAME }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v7 with: name: test-results-${{ matrix.index }} path: test-results @@ -80,32 +151,37 @@ jobs: docker-deploy-tests: name: docker needs: build - runs-on: ubuntu-18.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: download packages - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v8 with: name: build + path: build - name: ci-setup run: ./.github/commands/ci-setup + - name: docker-container-setup + run: | + ./tests/ci/setup.sh docker + - name: test docker deploys shell: bash timeout-minutes: 20 run: | - ./tests/ci/setup.sh docker + docker exec dokku bash -c "cat /etc/sudoers.d/dokku-nginx" DOKKU_SSH_PORT=3022 sudo -E make -e test-ci-docker go-tests: name: go.${{ matrix.index }} needs: build - runs-on: ubuntu-18.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -114,12 +190,13 @@ jobs: GITHUB_NODE_INDEX: ${{ matrix.index }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: download packages - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v8 with: name: build + path: build - name: ci-setup run: ./.github/commands/ci-setup @@ -137,7 +214,7 @@ jobs: 2) sudo -E make -e deploy-test-multi ;; 3) sudo -E make -e deploy-test-go-fail-predeploy deploy-test-go-fail-postdeploy ;; esac - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v7 with: name: coverage.${{ matrix.index }} path: test-results/coverage @@ -145,20 +222,22 @@ jobs: publish-test-results: name: publish-test-results needs: unit-tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-24.04 + permissions: + checks: write # the build-and-test job might be skipped, we don't need to run this job then if: success() || failure() steps: - name: download test-results - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v8 with: path: test-results - name: Publish Unit Test Results - uses: docker://ghcr.io/enricomi/publish-unit-test-result-action:v1 + uses: EnricoMi/publish-unit-test-result-action@v2 with: check_name: Unit Test Results - github_token: ${{ secrets.GH_ACCESS_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} files: test-results/**/*.xml - comment_on_pr: false + comment_mode: "off" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a0cdd6d1a5a..b51ba06d582 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -23,10 +23,14 @@ on: schedule: - cron: '18 22 * * 2' +concurrency: + group: codeql-analysis-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -38,11 +42,15 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v7 + + # workaround for https://github.com/github/codeql/issues/14235 + - name: Remove go.work file + run: rm go.work # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -53,7 +61,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v4 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -62,9 +70,5 @@ jobs: # and modify them (or add more) to build your code if your project # uses a compiled language - # - run: | - # make bootstrap - # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/dependency-updates.yml b/.github/workflows/dependency-updates.yml new file mode 100644 index 00000000000..8f16110d20b --- /dev/null +++ b/.github/workflows/dependency-updates.yml @@ -0,0 +1,110 @@ +--- +name: 'dependency-updates' + +# yamllint disable-line rule:truthy +on: + schedule: + - cron: '0 6 * * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + name: prepare + runs-on: ubuntu-24.04 + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - name: Clone + uses: actions/checkout@v7 + + - name: Build dependency matrix + id: matrix + run: | + matrix=$(jq -c ' + [ + (.dependencies[] | {section: "dependencies", name, version, url: .urls.amd64}), + (.predependencies[] | {section: "predependencies", name, version, url: .urls.amd64}), + (.recommendations[] | {section: "recommendations", name, version, url: .urls.amd64}) + ] + | map(. + {repo: (.url | capture("https://github.com/(?[^/]+/[^/]+)/releases").r)}) + | map(del(.url)) + | {include: .} + ' contrib/dependencies.json) + echo "matrix=${matrix}" >> "$GITHUB_OUTPUT" + + update: + name: 'update / ${{ matrix.name }}' + runs-on: ubuntu-24.04 + needs: prepare + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.prepare.outputs.matrix) }} + concurrency: + group: dependency-updates-${{ matrix.name }} + cancel-in-progress: false + steps: + - name: Clone + uses: actions/checkout@v7 + + - name: Resolve latest release + id: latest + env: + GH_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} + REPO: ${{ matrix.repo }} + CURRENT: ${{ matrix.version }} + run: | + tag=$(gh release view --repo "$REPO" --json tagName -q .tagName) + latest="${tag#v}" + echo "latest=${latest}" >> "$GITHUB_OUTPUT" + if dpkg --compare-versions "$latest" gt "$CURRENT"; then + echo "outdated=true" >> "$GITHUB_OUTPUT" + else + echo "outdated=false" >> "$GITHUB_OUTPUT" + fi + + - name: Bump dependency entry + if: steps.latest.outputs.outdated == 'true' + env: + SECTION: ${{ matrix.section }} + NAME: ${{ matrix.name }} + OLD: ${{ matrix.version }} + NEW: ${{ steps.latest.outputs.latest }} + run: | + tmp=$(mktemp) + jq --arg section "$SECTION" \ + --arg name "$NAME" \ + --arg old "$OLD" \ + --arg new "$NEW" ' + ($old) as $oldv | + ($new) as $newv | + (.[$section]) |= map( + if .name == $name then + .version = $newv + | .urls.amd64 |= (gsub("v" + $oldv; "v" + $newv) | gsub($oldv; $newv)) + | .urls.arm64 |= (gsub("v" + $oldv; "v" + $newv) | gsub($oldv; $newv)) + else . end + ) + ' contrib/dependencies.json > "$tmp" + mv "$tmp" contrib/dependencies.json + + - name: Open pull request + if: steps.latest.outputs.outdated == 'true' + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} + branch: chore/bump-${{ matrix.name }}-${{ steps.latest.outputs.latest }} + base: ${{ github.ref_name }} + delete-branch: true + committer: 'Dokku Bot ' + author: 'Dokku Bot ' + commit-message: 'chore: bump ${{ matrix.name }} to ${{ steps.latest.outputs.latest }}' + title: 'chore: bump ${{ matrix.name }} to ${{ steps.latest.outputs.latest }}' + body: | + Bumps `${{ matrix.name }}` from `${{ matrix.version }}` to `${{ steps.latest.outputs.latest }}`. + + Release notes: https://github.com/${{ matrix.repo }}/releases/tag/v${{ steps.latest.outputs.latest }} + labels: 'type: dependencies' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..44ac33fff7d --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,43 @@ +--- +name: 'docs' + +# yamllint disable-line rule:truthy +on: + push: + branches: + - master + +concurrency: + group: docs-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-24.04 + steps: + - name: Cloning repo + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: "Setup python 3.10" + uses: actions/setup-python@v7 + with: + python-version: '3.10' + + - name: Generate documentation + env: + BOT_GITHUB_USERNAME: ${{ secrets.HOMEBREW_GITHUB_USERNAME }} + BOT_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} + run: | + git config --global user.name 'Dokku Bot' + git config --global user.email no-reply@dokku.com + + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt update + sudo apt install gh -y + + gh auth login --with-token <<<"$BOT_GITHUB_API_TOKEN" + contrib/build-docs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c626b331701..092b9acee4a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,52 +10,77 @@ on: branches: - 'master' +concurrency: + group: lint-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: hadolint: name: hadolint - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v7 - name: Run hadolint - uses: hadolint/hadolint-action@d7b38582334d9ac11c12021c16f21d63015fa250 - # v1.6.0 => d7b38582334d9ac11c12021c16f21d63015fa250 + uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 + # v3.0.0 => 4b5806eb9c6bee4954fc0e0cc3ad6175fc9782c1 markdown-lint: name: markdown-lint - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v7 + - name: Setup node + uses: actions/setup-node@v7 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: '.github/workflows/lint.yml' + - name: Install markdownlint-cli + run: npm install -g markdownlint-cli@0.35.0 - name: Run markdown-lint - uses: avto-dev/markdown-lint@04d43ee9191307b50935a753da3b775ab695eceb - # v1.5.0 => 04d43ee9191307b50935a753da3b775ab695eceb + run: markdownlint -c .github/linters/.markdown-lint.yml *.md **/*.md + + packer: + name: packer + runs-on: ubuntu-24.04 + if: github.event.pull_request.user.login != 'dependabot[bot]' + steps: + - name: Checkout code + uses: actions/checkout@v7 + - name: Setup packer + uses: hashicorp/setup-packer@main with: - config: '.github/linters/.markdown-lint.yml' - args: './README.md' + version: latest + - name: Run packer init + run: "make image/init/digitalocean" + env: + PACKER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Run `packer validate` + env: + DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + run: "make image/validate/digitalocean" shellcheck: name: shellcheck - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v7 - name: Run shellcheck - uses: ludeeus/action-shellcheck@94e0aab03ca135d11a35e5bfc14e6746dc56e7e9 + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 1.1.0 => 94e0aab03ca135d11a35e5bfc14e6746dc56e7e9 - env: - # keep in sync with tests/shellcheck-exclude - SHELLCHECK_OPTS: -e SC1001 -e SC1003 -e SC1090 -e SC1091 -e SC1117 -e SC2029 -e SC2030 -e SC2031 -e SC2034 -e SC2046 -e SC2064 -e SC2068 -e SC2086 -e SC2119 -e SC2120 -e SC2128 -e SC2148 -e SC2153 -e SC2154 -e SC2155 -e SC2162 -e SC2174 -e SC2179 -e SC2191 -e SC2199 -e SC2207 -e SC2219 -e SC2220 -e SC2230 -e SC2231 -e SC2235 -e SC2267 -e SC2295 shfmt: name: shfmt - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v7 - name: Run shfmt - uses: luizm/action-sh-checker@7f44869033b40ee4ffe7dc76c87a1bc66e3d025a - # v0.3.0 => 7f44869033b40ee4ffe7dc76c87a1bc66e3d025a + uses: luizm/action-sh-checker@883217215b11c1fabbf00eb1a9a041f62d74c744 + # v0.5.0 => edd0e45ecff35b05f162052b50df50976c1b74fc env: SHFMT_OPTS: -l -bn -ci -i 2 -d with: @@ -63,12 +88,12 @@ jobs: yamllint: name: yamllint - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v7 - name: Run yamllint - uses: ibiqlik/action-yamllint@ed2b6e911569708ed121c14b87d513860a7e36a7 - # v3.0.4 => ed2b6e911569708ed121c14b87d513860a7e36a7 + uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c + # v3.1.1 => 2576378a8e339169678f9939646ee3ee325e845c with: config_file: '.github/linters/.yamllint.yml' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24a2ddab11f..a2f71c070f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,38 +1,59 @@ --- -name: 'release' +name: "release" # yamllint disable-line rule:truthy on: workflow_dispatch: inputs: release_type: - description: 'Release type [build, betafish, patch, minor, major]' + description: "Release type" + default: "patch" required: true - default: 'build' - + type: choice + options: + - patch + - minor + - major + - build + - betafish jobs: release: name: release - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 env: PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} outputs: version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 with: fetch-depth: 0 - uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6 + ruby-version: 2.7 - - name: Login to Docker Hub - uses: docker/login-action@v1 + - name: login to docker hub + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: set up qemu + uses: docker/setup-qemu-action@v4 + + - name: set up docker buildx + id: buildx + uses: docker/setup-buildx-action@v4 + + - name: inspect builder + run: | + echo "Name: ${{ steps.buildx.outputs.name }}" + echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" + echo "Status: ${{ steps.buildx.outputs.status }}" + echo "Flags: ${{ steps.buildx.outputs.flags }}" + echo "Platforms: ${{ steps.buildx.outputs.platforms }}" + - name: install package_cloud run: gem install package_cloud @@ -42,25 +63,64 @@ jobs: git config --global user.email no-reply@dokku.com - name: build package + env: + RELEASE_GITHUB_USERNAME: ${{ secrets.HOMEBREW_GITHUB_USERNAME }} + RELEASE_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_GITHUB_API_TOKEN }} run: contrib/release-dokku ${{ github.event.inputs.release_type }} - name: output version id: version - run: echo "::set-output name=version::$(cat build/next-version)" + run: echo "version=$(cat build/next-version)" >> $GITHUB_OUTPUT - name: upload packages - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v7 with: name: build path: build + build-digitalocean: + name: build-digitalocean + runs-on: ubuntu-24.04 + needs: release + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Setup packer + uses: hashicorp/setup-packer@main + with: + version: latest + + - name: Run packer init + run: "make image/init/digitalocean" + + - name: Run `packer validate` + env: + DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + run: | + VERSION=${{ needs.release.outputs.version }} + PKR_VAR_dokku_version="${VERSION:1}" make image/validate/digitalocean + + - name: Bump Digitalocean Image + env: + DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} + run: | + VERSION=${{ needs.release.outputs.version }} + PKR_VAR_dokku_version="${VERSION:1}" make image/build/digitalocean + + - name: upload packages + uses: actions/upload-artifact@v7 + with: + name: digitalocean-manifest.json + path: digitalocean-manifest.json + bump-azure: name: bump-azure - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 needs: release steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v7 - name: Bump Azure Template env: BOT_GITHUB_USERNAME: ${{ secrets.HOMEBREW_GITHUB_USERNAME }} @@ -76,6 +136,31 @@ jobs: runs-on: macos-latest needs: release steps: + - name: Brew doctor + run: | + brew doctor || true + - name: Brew cleanup + run: | + echo "====> Running brew cleanup" + brew cleanup || true + echo "====> Untapping homebrew/core" + brew untap homebrew/core || true + echo "====> Untapping homebrew/cask" + brew untap homebrew/cask || true + echo "====> Untapping aws/tap" + brew untap aws/tap || true + echo "====> Untapping hashicorp/tap" + brew untap hashicorp/tap || true + echo "====> Uninstalling openssl@1.1" + brew uninstall openssl@1.1 || true + echo "====> Uninstalling ruby@3.0" + brew uninstall ruby@3.0 || true + echo "====> Running brew cleanup" + brew cleanup || true + - name: Brew doctor + run: brew doctor || true + - name: Brew config + run: brew config - name: Bump Homebrew Formula env: HOMEBREW_GITHUB_USERNAME: ${{ secrets.HOMEBREW_GITHUB_USERNAME }} diff --git a/.gitignore b/.gitignore index deb157886df..39d3bcbad14 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ test-results/ stack.tgz tmp coverage.out +site +go.work.sum +node_modules \ No newline at end of file diff --git a/.golangci-lint.yml b/.golangci-lint.yml new file mode 100644 index 00000000000..86abba66c43 --- /dev/null +++ b/.golangci-lint.yml @@ -0,0 +1,24 @@ +--- +run: + timeout: 5m + +linters: + disable-all: true + + enable: + - errcheck + - goconst + - gocyclo + - gofumpt + - gosec + - gosimple + - govet + - ineffassign + - misspell + - revive + - staticcheck + - typecheck + - unconvert + - unparam + - unused + - whitespace diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000000..dd449725e18 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.md diff --git a/tests/shellcheck-exclude b/.shellcheckrc similarity index 92% rename from tests/shellcheck-exclude rename to .shellcheckrc index 2049f4b80e6..dffeb4f39b3 100644 --- a/tests/shellcheck-exclude +++ b/.shellcheckrc @@ -16,7 +16,7 @@ # SC2128 - Expanding an array without an index only gives the first element - https://github.com/koalaman/shellcheck/wiki/SC2128 # SC2148 - Tips depend on target shell and yours is unknown. Add a shebang - https://github.com/koalaman/shellcheck/wiki/SC2148 # SC2153 - Possible misspelling: DOKKU_ROOT may not be assigned, but DOKKU_PORT is - https://github.com/koalaman/shellcheck/wiki/SC2153 -# SC2154 - passed_docker_option is referenced but not assigned - https://github.com/koalaman/shellcheck/wiki/SC2154 +# SC2154 - Var is referenced but not assigned - https://github.com/koalaman/shellcheck/wiki/SC2154 # SC2155 - Declare and assign separately to avoid masking return values - https://github.com/koalaman/shellcheck/wiki/SC2155 # SC2162 - read without -r will mangle backslashes - https://github.com/koalaman/shellcheck/wiki/SC2162 # SC2174 - When used with -p, -m only applies to the deepest directory - https://github.com/koalaman/shellcheck/wiki/SC2174 @@ -30,3 +30,4 @@ # SC2235 - Use { ..; } instead of (..) to avoid subshell overhead - https://github.com/koalaman/shellcheck/wiki/SC2235 # SC2267 - GNU xargs -i is deprecated in favor of -I{} - https://github.com/koalaman/shellcheck/wiki/SC2267 # SC2295 - Expansions inside ${..} need to be quoted separately, otherwise they match as patterns - https://github.com/koalaman/shellcheck/wiki/SC2295 +disable=SC1001,SC1003,SC1090,SC1091,SC1117,SC2029,SC2030,SC2031,SC2034,SC2046,SC2064,SC2068,SC2086,SC2119,SC2120,SC2128,SC2148,SC2153,SC2154,SC2155,SC2162,SC2174,SC2179,SC2191,SC2199,SC2207,SC2219,SC2220,SC2231,SC2235,SC2267,SC2295 diff --git a/.stickler.yml b/.stickler.yml deleted file mode 100644 index de214fa2ccc..00000000000 --- a/.stickler.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -linters: - shellcheck: - shell: bash - exclude: SC2034,SC1090,SC2191 - golint: - -files: - ignore: - - 'debian/*' - - 'docs/*' - - 'tests/*' - - '*/vendor/*' diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls new file mode 100644 index 00000000000..801633007cf --- /dev/null +++ b/.well-known/funding-manifest-urls @@ -0,0 +1 @@ +https://dokku.com/funding.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70825f4d18b..2eff3cb8a3a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,11 +19,11 @@ a chance of keeping on top of things. - [Reporting Security Issues](#reporting-security-issues) - [Reporting Issues](#reporting-other-issues) - [Contributing](#contributing) - - [Making Changes](#making-changes) - - [Which branch to base the work](#which-branch-to-base-the-work) - - [Submitting Changes](#submitting-changes) - - [When will my change be merged?](#when-will-my-change-be-merged) - - [Running tests locally](#running-tests-locally) + - [Making Changes](#making-changes) + - [Which branch to base the work](#which-branch-to-base-the-work) + - [Submitting Changes](#submitting-changes) + - [When will my change be merged?](#when-will-my-change-be-merged) + - [Running tests locally](#running-tests-locally) - [Additional Resources](#additional-resources) ## Reporting security issues @@ -65,24 +65,24 @@ need to do - Make sure you have a [GitHub account](https://github.com/signup/free). - Submit an [issue](https://github.com/dokku/dokku/issues), assuming one does not already exist. - - Clearly describe the issue including steps to reproduce when it is a bug. - - Make sure you fill in the earliest version that you know has the issue. + - Clearly describe the issue including steps to reproduce when it is a bug. + - Make sure you fill in the earliest version that you know has the issue. - Fork the repository on GitHub. ### Making Changes - Create a topic branch from where you want to base your work. - - This is usually the master branch. - - Only target an existing branch if you are certain your fix must be on that - branch. - - To quickly create a topic branch based on master; `git checkout -b my_contribution origin/master`. - It is best to avoid working directly on the `master` branch. Doing so will + - This is usually the master branch. + - Only target an existing branch if you are certain your fix must be on that + branch. + - To quickly create a topic branch based on master; `git checkout -b my_contribution origin/master`. + It is best to avoid working directly on the `master` branch. Doing so will help avoid conflicts if you pull in updates from origin. - Make commits of logical units. Implementing a new function and calling it in another file constitute a single logical unit of work. - - Before you make a pull request, squash your commits into logical units of work - using `git rebase -i` and `git push -f`. - - A majority of submissions should have a single commit, so if in doubt, + - Before you make a pull request, squash your commits into logical units of work + using `git rebase -i` and `git push -f`. + - A majority of submissions should have a single commit, so if in doubt, squash your commits down to one commit. - Check for unnecessary whitespace with `git diff --check` before committing. - Use descriptive commit messages and reference the #issue number. @@ -92,12 +92,12 @@ need to do - Your work should apply the [Dokku coding standards](https://github.com/progrium/bashstyle) - Pull requests must be cleanly rebased on top of master without multiple branches mixed into the PR. - - **Git tip**: If your PR no longer merges cleanly, use `rebase master` in your + - **Git tip**: If your PR no longer merges cleanly, use `rebase master` in your feature branch to update your pull request rather than `merge master`. ### Which branch to base the work -All changes should be be based on the latest master commit. +All changes should be based on the latest master commit. ### Submitting Changes @@ -135,4 +135,4 @@ which contains test setup information as well as tips for running tests. - [Existing issues](https://github.com/dokku/dokku/issues) - [General GitHub documentation](https://help.github.com/) - [GitHub pull request documentation](https://help.github.com/send-pull-requests/) -- [Gliderlabs Slack in the #dokku channel](https://glider-slackin.herokuapp.com/) +- [Gliderlabs Slack in the #dokku channel](https://slack.dokku.com/) diff --git a/Dockerfile b/Dockerfile index d146340581d..3b2229b5cca 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM phusion/baseimage:bionic-1.0.0 +FROM phusion/baseimage:noble-1.0.3 CMD ["/sbin/my_init"] @@ -14,23 +14,47 @@ RUN addgroup --gid $DOKKU_GID dokku \ && adduser --uid $DOKKU_UID --gid $DOKKU_GID --disabled-password --gecos "" "dokku" COPY ./tests/dhparam.pem /tmp/dhparam.pem -COPY ./build/dokku.deb /tmp/dokku.deb +COPY ./build/package/ /tmp +COPY ./contrib/dependencies.json /tmp/dependencies.json + +ENV DOKKU_INIT_SYSTEM=sv SHELL ["/bin/bash", "-o", "pipefail", "-c"] # hadolint ignore=DL3005,DL3008 -RUN echo "dokku dokku/hostname string $DOKKU_HOSTNAME" | debconf-set-selections \ +RUN mkdir -p /etc/apt/keyrings \ + && mkdir -p /etc/apt/keyrings \ + && apt-get remove -y systemd && apt-get autoremove -y && apt-get update && apt-get -y --no-install-recommends install gpg jq lsb-release openssl openssh-server rsync software-properties-common \ + && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \ + && apt-get update \ + && apt-get -y --no-install-recommends install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \ + && PACK_URL="$(jq -r --arg arch "$(dpkg --print-architecture)" '.dependencies[] | select(.name == "pack") | .urls[$arch]' /tmp/dependencies.json)" \ + && curl -fsSL "$PACK_URL" | tar -C /usr/bin/ --no-same-owner -xzf - pack \ + && chmod +x /usr/bin/pack \ + && test -x /usr/bin/pack \ + && curl -o /tmp/nixpacks.bash -sSL https://nixpacks.com/install.sh \ + && chmod +x /tmp/nixpacks.bash \ + && NIXPACKS_BIN_DIR=/usr/bin BIN_DIR=/usr/bin /tmp/nixpacks.bash \ + && test -x /usr/bin/nixpacks \ + && rm -rf /tmp/nixpacks.bash \ + && curl -o /tmp/railpack.bash -sSL https://railpack.com/install.sh \ + && chmod +x /tmp/railpack.bash \ + && RAILPACK_BIN_DIR=/usr/bin BIN_DIR=/usr/bin /tmp/railpack.bash \ + && test -x /usr/bin/railpack \ + && rm -rf /tmp/railpack.bash \ + && echo "dokku dokku/hostname string $DOKKU_HOSTNAME" | debconf-set-selections \ && echo "dokku dokku/skip_key_file boolean $DOKKU_SKIP_KEY_FILE" | debconf-set-selections \ && echo "dokku dokku/vhost_enable boolean $DOKKU_VHOST_ENABLE" | debconf-set-selections \ + && echo "dokku dokku/install_default_site boolean true" | debconf-set-selections \ && curl -sSL https://packagecloud.io/dokku/dokku/gpgkey | apt-key add - \ - && echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ bionic main" | tee /etc/apt/sources.list.d/dokku.list \ + && echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ noble main" | tee /etc/apt/sources.list.d/dokku.list \ && mkdir -p /etc/nginx/ \ && cp /tmp/dhparam.pem /etc/nginx/dhparam.pem \ - && apt-get update -qq \ - && apt-get upgrade -qq -y \ - && apt-get -qq -y --no-install-recommends --only-upgrade install openssl openssh-server \ - && apt-get -qq -y --no-install-recommends install rsync /tmp/dokku.deb \ - && apt-get purge -qq -y syslog-ng-core \ - && apt-get autoremove -qq -y \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get -y --no-install-recommends install lambda-builder "/tmp/dokku-$(dpkg --print-architecture).deb" \ + && apt-get purge -y syslog-ng-core \ + && apt-get autoremove -y \ && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* WORKDIR /tmp @@ -39,6 +63,7 @@ COPY ./docker . RUN \ rsync -a /tmp/ / \ + && chmod +x /usr/local/bin/* \ && rm -rf /tmp/* \ && rm /etc/runit/runsvdir/default/sshd/down \ && chown -R dokku:dokku /home/dokku/ \ @@ -51,8 +76,12 @@ RUN \ && ln -sf /mnt/dokku/home/dokku /home/dokku \ && ln -sf /mnt/dokku/var/lib/dokku/config /var/lib/dokku/config \ && ln -sf /mnt/dokku/var/lib/dokku/data /var/lib/dokku/data \ + && ln -sf /mnt/dokku/var/lib/dokku/services /var/lib/dokku/services \ && mv /etc/my_init.d/00_regen_ssh_host_keys.sh /etc/my_init.d/15_regen_ssh_host_keys \ && rm -f /etc/nginx/sites-enabled/default /usr/share/nginx/html/index.html /etc/my_init.d/10_syslog-ng.init \ && rm -f /usr/local/openresty/nginx/conf/sites-enabled/default /usr/share/openresty/html/index.html \ && sed -i '/imklog/d' /etc/rsyslog.conf \ && rm -f /var/log/btmp /var/log/wtmp /var/log/*log /var/log/apt/* /var/log/dokku/*.log /var/log/nginx/* /var/log/openresty/* + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5m --retries=3 \ + CMD curl -fsS http://127.0.0.1:18080/_dokku/health || exit 1 diff --git a/HISTORY.md b/HISTORY.md index c5cc0e3ac68..15610ccfc2c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,11 +1,5312 @@ # History +## 0.38.25 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.25/bootstrap.sh +sudo DOKKU_TAG=v0.38.25 bash bootstrap.sh +``` + +### Security + +- #8848: @josegonzalez Prevent command injection via docker options eval + +### New Features + +- #8849: @josegonzalez Support per-app letsencrypt emails on k3s + +## 0.38.24 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.24/bootstrap.sh +sudo DOKKU_TAG=v0.38.24 bash bootstrap.sh +``` + +### Documentation + +- #8836: @josegonzalez Link herokuish buildpack references to buildpack management page + +### Tests + +- #8841: @dependabot[bot] chore(deps): bump ruby from 4.0.5 to 4.0.6 in /tests/apps/dockerfile-entrypoint +- #8843: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.82.0 to 1.82.1 in /tests/apps/gogrpc + +### Dependencies + +- #8845: @dependabot[bot] chore(deps): bump github.com/mattn/go-isatty from 0.0.22 to 0.0.23 in /plugins/app-json +- #8844: @dependabot[bot] chore(deps): bump github.com/melbahja/goph from 1.5.1 to 1.5.2 in /plugins/common +- #8842: @dependabot[bot] chore(deps): bump timberio/vector from 0.56.0-debian to 0.57.0-debian in /plugins/logs +- #8838: @dokku-bot chore: bump pack to 0.40.8 +- #8846: @dependabot[bot] chore(deps): bump traefik from v3.7.7 to v3.7.8 in /plugins/traefik-vhosts +- #8839: @dependabot[bot] chore(deps): bump actions/setup-node from 6 to 7 +- #8834: @dokku-bot chore: bump dokku-event-listener to 0.20.0 + +## 0.38.23 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.23/bootstrap.sh +sudo DOKKU_TAG=v0.38.23 bash bootstrap.sh +``` + +### Bug Fixes + +- #8833: @josegonzalez Parse cert CN and subject on OpenSSL 3.x + +### Tests + +- #8825: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/zombies-dockerfile-no-tini +- #8823: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/zombies-dockerfile-tini +- #8820: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 292 to 293 in /tests/apps/php +- #8821: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/go-fail-predeploy +- #8822: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/gogrpc +- #8824: @dependabot[bot] chore(deps): bump golang from 1.26.4 to 1.26.5 in /tests/apps/go-fail-postdeploy + +### Dependencies + +- #8831: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.21.2 to 3.21.3 in /plugins/scheduler-k3s +- #8830: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.37.0 to 1.38.0 in /plugins/scheduler-k3s +- #8826: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.53.0 to 0.54.0 in /plugins/common +- #8827: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.20.3 to 1.21.0 in /plugins/scheduler-k3s +- #8828: @dependabot[bot] chore(deps): bump traefik from v3.7.6 to v3.7.7 in /plugins/traefik-vhosts +- #8829: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.24.0 to 1.0.0 in /plugins/scheduler-k3s + +## 0.38.22 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.22/bootstrap.sh +sudo DOKKU_TAG=v0.38.22 bash bootstrap.sh +``` + +### New Features + +- #8805: @RichardDorian Allow custom values for chown +- #8810: @josegonzalez Expose whether a network was created by dokku +- #8807: @josegonzalez Allow replacing buildpack list atomically +- #8808: @josegonzalez Expose docker-options as structured lists in JSON report +- #8806: @josegonzalez Expose scheduler-k3s autoscaling-auth state for read-back +- #8801: @josegonzalez Add --format json support to plugin:list + +### Refactors + +- #8804: @josegonzalez Port bash :report subcommands to golang + +### Documentation + +- #8809: @josegonzalez Document nginx validate-config load_module override + +### Tests + +- #8803: @dependabot[bot] chore(deps): bump django from 5.2.15 to 5.2.16 in /tests/apps/dockerfile-release + +### Dependencies + +- #8816: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.21.0 to 0.22.0 in /plugins/common +- #8818: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.51 to 2.11.52 in /plugins/scheduler-k3s +- #8817: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.21.0 to 0.22.0 in /plugins/scheduler-docker-local +- #8819: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.21.0 to 0.22.0 in /plugins/scheduler-k3s + +## 0.38.21 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.21/bootstrap.sh +sudo DOKKU_TAG=v0.38.21 bash bootstrap.sh +``` + +### New Features + +- #8795: @josegonzalez Add --format json support to apps:list + +### Tests + +- #8788: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.81.1 to 1.82.0 in /tests/apps/gogrpc +- #8787: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.51.0 to 0.55.0 in /tests/apps/gogrpc + +### Dependencies + +- #8781: @dependabot[bot] chore(deps): bump lucaslorentz/caddy-docker-proxy from 2.12 to 2.13 in /plugins/caddy-vhosts +- #8794: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.36.0 to 1.37.0 in /plugins/scheduler-k3s +- #8789: @dependabot[bot] chore(deps): bump pymdown-extensions from 11.0 to 11.0.1 in /docs/_build +- #8782: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.23.2 to 0.24.0 in /plugins/scheduler-k3s +- #8785: @dependabot[bot] chore(deps): bump traefik from v3.7.5 to v3.7.6 in /plugins/traefik-vhosts +- #8786: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.31.0 to 1.36.0 in /plugins/scheduler-k3s +- #8783: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.50 to 2.11.51 in /plugins/scheduler-k3s +- #8780: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.20.2 to 1.20.3 in /plugins/scheduler-k3s +- #8779: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.23.1 to 0.23.2 in /plugins/scheduler-k3s + +### Other + +- #8796: @josegonzalez Add --format json support to ps:scale + +## 0.38.20 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.20/bootstrap.sh +sudo DOKKU_TAG=v0.38.20 bash bootstrap.sh +``` + +### Tests + +- #8773: @dependabot[bot] chore(deps): bump python from 3.15.0b2-bookworm to 3.15.0b3-bookworm in /tests/apps/dockerfile-release +- #8756: @dependabot[bot] chore(deps): bump sass from 1.100.0 to 1.101.0 in /tests/apps/multi +- #8753: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 291 to 292 in /tests/apps/php + +### Dependencies + +- #8766: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.21.1 to 3.21.2 in /plugins/scheduler-k3s +- #8777: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.41.0 to 1.42.1 in /plugins/common +- #8778: @dependabot[bot] chore(deps): bump byjg/easy-haproxy from 6.1.0 to 6.1.1 in /plugins/haproxy-vhosts +- #8748: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.52.0 to 0.53.0 in /plugins/common +- #8745: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.20.0 to 0.21.0 in /plugins/common +- #8774: @dependabot[bot] chore(deps): bump python from 3.15.0b2-alpine to 3.15.0b3-alpine in /docs/_build +- #8769: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.21.3 to 11.0 in /docs/_build +- #8775: @dependabot[bot] chore(deps): bump click from 8.4.1 to 8.4.2 in /docs/_build +- #8776: @dependabot[bot] chore(deps): bump byjg/easy-haproxy from 6.0.1 to 6.1.0 in /plugins/haproxy-vhosts +- #8772: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.42.0 to 1.42.1 in /plugins/buildpacks +- #8770: @dokku-bot chore: bump pack to 0.40.7 +- #8767: @dependabot[bot] chore(deps): bump github.com/containerd/containerd from 1.7.32 to 1.7.33 in /plugins/scheduler-k3s +- #8765: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.41.0 to 1.42.0 in /plugins/scheduler-k3s +- #8764: @dependabot[bot] chore(deps): bump actions/checkout from 6 to 7 +- #8762: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.36.1 to 1.36.2 in /plugins/scheduler-k3s +- #8758: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.41.0 to 1.42.0 in /plugins/buildpacks +- #8760: @dependabot[bot] chore(deps): bump k8s.io/apimachinery from 0.36.1 to 0.36.2 in /plugins/scheduler-k3s +- #8757: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.21.0 to 3.21.1 in /plugins/scheduler-k3s +- #8755: @dependabot[bot] chore(deps): bump traefik from v3.7.4 to v3.7.5 in /plugins/traefik-vhosts +- #8754: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.49 to 2.11.50 in /plugins/scheduler-k3s +- #8749: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.47 to 2.11.49 in /plugins/scheduler-k3s +- #8743: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.14.3 to 4.15.0 in /docs/_build +- #8744: @dependabot[bot] chore(deps): bump traefik from v3.7.3 to v3.7.4 in /plugins/traefik-vhosts +- #8747: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.20.0 to 0.21.0 in /plugins/scheduler-k3s +- #8746: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.20.0 to 0.21.0 in /plugins/scheduler-docker-local + +## 0.38.19 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.19/bootstrap.sh +sudo DOKKU_TAG=v0.38.19 bash bootstrap.sh +``` + +## 0.38.18 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.18/bootstrap.sh +sudo DOKKU_TAG=v0.38.18 bash bootstrap.sh +``` + +### Tests + +- #8734: @dependabot[bot] chore(deps): bump golang from 1.26.3 to 1.26.4 in /tests/apps/gogrpc +- #8731: @dependabot[bot] chore(deps): bump golang from 1.26.3 to 1.26.4 in /tests/apps/go-fail-predeploy +- #8733: @dependabot[bot] chore(deps): bump golang from 1.26.3 to 1.26.4 in /tests/apps/zombies-dockerfile-tini +- #8735: @dependabot[bot] chore(deps): bump golang from 1.26.3 to 1.26.4 in /tests/apps/go-fail-postdeploy +- #8736: @dependabot[bot] chore(deps): bump golang from 1.26.3 to 1.26.4 in /tests/apps/zombies-dockerfile-no-tini +- #8738: @dependabot[bot] chore(deps): bump python from 3.15.0b1-bookworm to 3.15.0b2-bookworm in /tests/apps/dockerfile-release +- #8730: @dependabot[bot] chore(deps): bump django from 5.2.14 to 5.2.15 in /tests/apps/dockerfile-release + +### Dependencies + +- #8727: @dependabot[bot] chore(deps): bump github.com/melbahja/goph from 1.5.0 to 1.5.1 in /plugins/common +- #8739: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.46 to 2.11.47 in /plugins/scheduler-k3s +- #8737: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.12.0 to 0.12.1 in /plugins/openresty-vhosts +- #8732: @dependabot[bot] chore(deps): bump python from 3.15.0b1-alpine to 3.15.0b2-alpine in /docs/_build +- #8740: @dependabot[bot] chore(deps): bump timberio/vector from 0.55.0-debian to 0.56.0-debian in /plugins/logs +- #8741: @dependabot[bot] chore(deps): bump traefik from v3.7.1 to v3.7.3 in /plugins/traefik-vhosts + +## 0.38.17 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.17/bootstrap.sh +sudo DOKKU_TAG=v0.38.17 bash bootstrap.sh +``` + +### New Features + +- #8729: @josegonzalez Add scheduler-k3s:preview subcommand to allow previewing helm chart changes + +### Dependencies + +- #8728: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.11.0 to 0.12.0 in /plugins/openresty-vhosts + +## 0.38.16 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.16/bootstrap.sh +sudo DOKKU_TAG=v0.38.16 bash bootstrap.sh +``` + +### Bug Fixes + +- #8723: @josegonzalez Match control-plane nodes by their modern label +- #8724: @josegonzalez Stop truncating existing files in common.TouchFile + +## 0.38.15 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.15/bootstrap.sh +sudo DOKKU_TAG=v0.38.15 bash bootstrap.sh +``` + +### Bug Fixes + +- #8718: @josegonzalez Persist scheduler-k3s chart overrides as JSON maps + +### Other + +- #8720: @josegonzalez Persist scheduler-k3s annotations and labels as JSON maps + +## 0.38.14 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.14/bootstrap.sh +sudo DOKKU_TAG=v0.38.14 bash bootstrap.sh +``` + +### New Features + +- #8716: @josegonzalez Add scheduler-k3s annotations:report and labels:report +- #8715: @josegonzalez Add scheduler-k3s:charts:set and :charts:report + +## 0.38.13 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.13/bootstrap.sh +sudo DOKKU_TAG=v0.38.13 bash bootstrap.sh +``` + +### Bug Fixes + +- #8713: @josegonzalez Make storage:mount upsert the existing attachment + +### New Features + +- #8714: @josegonzalez Expose per-attachment fields in storage:report +- #8712: @josegonzalez Expose attachment readonly and volume_options in storage:list json +- #8711: @josegonzalez Add --volume-options flag to storage:mount + +## 0.38.12 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.12/bootstrap.sh +sudo DOKKU_TAG=v0.38.12 bash bootstrap.sh +``` + +### Bug Fixes + +- #8706: @josegonzalez Align apps:set/apps:report with what dokku actually reads + +## 0.38.11 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.11/bootstrap.sh +sudo DOKKU_TAG=v0.38.11 bash bootstrap.sh +``` + +### Bug Fixes + +- #8704: @josegonzalez Pass storage entry basename to chown-storage-dir + +## 0.38.10 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.10/bootstrap.sh +sudo DOKKU_TAG=v0.38.10 bash bootstrap.sh +``` + +### New Features + +- #8702: @josegonzalez Prompt for confirmation on storage:destroy + +### Tests + +- #8698: @dependabot[bot] chore(deps-dev): bump org.apache.maven.plugins:maven-dependency-plugin from 3.10.0 to 3.11.0 in /tests/apps/java + +## 0.38.9 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.9/bootstrap.sh +sudo DOKKU_TAG=v0.38.9 bash bootstrap.sh +``` + +### New Features + +- #8697: @josegonzalez Split openresty report into raw/computed/global + +### Refactors + +- #8696: @josegonzalez Treat empty ps restart-policy as an unset + +## 0.38.8 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.8/bootstrap.sh +sudo DOKKU_TAG=v0.38.8 bash bootstrap.sh +``` + +### Bug Fixes + +- #8690: @josegonzalez Unset bind-all-interfaces on empty value +- #8689: @josegonzalez Expose remaining settable properties via :report + +### New Features + +- #8692: @Mordred Enable compression middleware for traefik when using scheduler-k3s +- #8691: @josegonzalez Align Go-plugin :report JSON keys with bash-strip convention +- #8688: @josegonzalez Promote registry image-repo-template to app + global + +### Dependencies + +- #8693: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.20.2 to 3.21.0 in /plugins/scheduler-k3s + +## 0.38.7 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.7/bootstrap.sh +sudo DOKKU_TAG=v0.38.7 bash bootstrap.sh +``` + +### Security + +- #8672: @josegonzalez Prevent host shell injection from app.json cron commands + +### Bug Fixes + +- #8669: @josegonzalez Recover deployed image from registry on local miss +- #8679: @josegonzalez Align ps/cron :report --global keys with plugin convention +- #8678: @josegonzalez Split openresty report keys into global and computed pairs +- #8676: @immanuwell Use SYSTEM for shfmt Darwin detection + +### New Features + +- #8677: @josegonzalez Warn on deprecated listen http2 in custom nginx templates + +### Tests + +- #8683: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 290 to 291 in /tests/apps/php +- #8671: @josegonzalez Qualify scheduler-k3s ingressroute kubectl lookups + +### Dependencies + +- #8682: @dependabot[bot] chore(deps): bump k8s.io/apimachinery from 0.36.0 to 0.36.1 in /plugins/scheduler-k3s +- #8681: @dependabot[bot] chore(deps): bump soupsieve from 2.8.3 to 2.8.4 in /docs/_build + +### Other + +- #8680: @josegonzalez Split more report global keys into raw and computed + +## 0.38.6 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.6/bootstrap.sh +sudo DOKKU_TAG=v0.38.6 bash bootstrap.sh +``` + +### Bug Fixes + +- #8633: @Mordred Correctly redirect http traffic to https when using scheduler-k3s +- #8665: @josegonzalez Clarify byte-preserving behavior of config:set --encoded +- #8666: @josegonzalez Depend on cron | cron-daemon to allow alternatives +- #8638: @josegonzalez Support --global on cron:set + +### Refactors + +- #8668: @josegonzalez Hash scheduler-k3s cron-id label to fit Kubernetes' 63-byte cap +- #8664: @josegonzalez Convert filesystem migration markers to plugin properties + +### Documentation + +- #8645: @zenspider Removed outdated warning about dokku-update not being able to upgrade to specific versions. + +### Tests + +- #8667: @josegonzalez Rename logs:report vector key test and guard old keys +- #8662: @dependabot[bot] chore(deps): bump qs from 6.15.0 to 6.15.2 in /tests/apps/checks-root +- #8661: @dependabot[bot] chore(deps): bump sass from 1.99.0 to 1.100.0 in /tests/apps/multi +- #8658: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 288 to 290 in /tests/apps/php +- #8657: @dependabot[bot] chore(deps): bump slim/slim from 4.15.1 to 4.15.2 in /tests/apps/php +- #8654: @dependabot[bot] chore(deps): bump ruby from 4.0.4 to 4.0.5 in /tests/apps/dockerfile-entrypoint +- #8642: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.81.0 to 1.81.1 in /tests/apps/gogrpc + +### Dependencies + +- #8652: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.40.0 to 1.41.0 in /plugins/common +- #8663: @dokku-bot chore: bump herokuish to 0.11.13 +- #8646: @dokku-bot chore: bump pack to 0.40.6 +- #8659: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.51.0 to 0.52.0 in /plugins/common +- #8660: @dependabot[bot] chore(deps): bump click from 8.4.0 to 8.4.1 in /docs/_build +- #8655: @dependabot[bot] chore(deps): bump github.com/containerd/containerd from 1.7.30 to 1.7.32 in /plugins/scheduler-k3s +- #8651: @dependabot[bot] chore(deps): bump click from 8.3.3 to 8.4.0 in /docs/_build +- #8649: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.40.0 to 1.41.0 in /plugins/buildpacks +- #8650: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.40.0 to 1.41.0 in /plugins/config +- #8648: @dependabot[bot] chore(deps): bump zipp from 3.23.1 to 4.1.0 in /docs/_build +- #8644: @dokku-bot chore: bump pack to 0.40.5 +- #8641: @dependabot[bot] chore(deps): bump traefik from v3.7.0 to v3.7.1 in /plugins/traefik-vhosts +- #8634: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.21.2 to 10.21.3 in /docs/_build + +### Other + +- #8640: @josegonzalez Split report global keys into raw and computed + +## 0.38.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.5/bootstrap.sh +sudo DOKKU_TAG=v0.38.5 bash bootstrap.sh +``` + +### Bug Fixes + +- #8627: @josegonzalez Preserve explicit https:443 port mappings on cert update + +### New Features + +- #8629: @josegonzalez Attach vector container to additional docker networks +- #8624: @josegonzalez Expose certs-set and certs-remove plugin triggers +- #8626: @josegonzalez Split caddy report tls-internal into raw, computed, and global + +### Tests + +- #8620: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 287 to 288 in /tests/apps/php +- #8621: @dependabot[bot] chore(deps): bump ruby from 4.0.3 to 4.0.4 in /tests/apps/dockerfile-entrypoint + +### Dependencies + +- #8622: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.36.0 to 1.36.1 in /plugins/scheduler-k3s + +## 0.38.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.4/bootstrap.sh +sudo DOKKU_TAG=v0.38.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #8615: @josegonzalez Reject per-app sets for openresty global-only properties +- #8613: @josegonzalez Expose raw deploy-branch and keep-git-dir in git:report +- #8549: @josegonzalez Route CNB images through launcher on scheduler-k3s + +### New Features + +- #8614: @josegonzalez Split scheduler-docker-local report into raw, computed, and global + +### Documentation + +- #8603: @cheif Add `dokku-http-oauth` to community plugins + +### Tests + +- #8618: @josegonzalez Isolate scheduler-k3s registry tags per bats file +- #8616: @josegonzalez Migrate from junit_files to files in EnricoMi/publish-unit-test-result-action +- #8617: @josegonzalez Upgrade actions in shared build-image compose action +- #8609: @josegonzalez Skip packer lint job on dependabot PRs +- #8604: @dependabot[bot] chore(deps): bump python from 3.14.3-bookworm to 3.15.0b1-bookworm in /tests/apps/dockerfile-release + +### Dependencies + +- #8606: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.50.0 to 0.51.0 in /plugins/common +- #8608: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.45 to 2.11.46 in /plugins/scheduler-k3s +- #8607: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.10.0 to 0.11.0 in /plugins/openresty-vhosts +- #8605: @dependabot[bot] chore(deps): bump python from 3.14.3-alpine to 3.15.0b1-alpine in /docs/_build + +## 0.38.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.3/bootstrap.sh +sudo DOKKU_TAG=v0.38.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #8602: @josegonzalez Reject per-app sets for global-only proxy properties +- #8601: @josegonzalez Rename app-json:report flags to match property +- #8600: @josegonzalez Report info-flag should not error when app undeployed + +### New Features + +- #8599: @josegonzalez Add docker healthcheck to dokku container + +## 0.38.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.2/bootstrap.sh +sudo DOKKU_TAG=v0.38.2 bash bootstrap.sh +``` + +### Security + +- #8590: @josegonzalez Restrict app names to prevent command injection +- #8591: @josegonzalez Harden archive extraction against symlink traversal +- #8589: @josegonzalez Enforce 0600 permissions on .netrc credentials file +- #8588: @josegonzalez Sanitize openresty include filenames to prevent eval injection + +### Bug Fixes + +- #8593: @josegonzalez Gate ssl_reject_handshake behind nginx 1.19.4 +- #8578: @josegonzalez Reference SOURCECODE_WORK_DIR in builder core-post-extract + +### Documentation + +- #8592: @josegonzalez Add security section to release changelog +- #8587: @vixalien Correct buildkit builder code block syntax +- #8580: @othercorey Set issue type in bug report template + +### Tests + +- #8586: @josegonzalez Count assert_output_contains matches as fixed strings +- #8581: @dependabot[bot] chore(deps): bump golang from 1.26.2 to 1.26.3 in /tests/apps/go-fail-predeploy +- #8582: @dependabot[bot] chore(deps): bump golang from 1.26.2 to 1.26.3 in /tests/apps/gogrpc +- #8584: @dependabot[bot] chore(deps): bump golang from 1.26.2 to 1.26.3 in /tests/apps/go-fail-postdeploy +- #8583: @dependabot[bot] chore(deps): bump golang from 1.26.2 to 1.26.3 in /tests/apps/zombies-dockerfile-tini +- #8585: @dependabot[bot] chore(deps): bump golang from 1.26.2 to 1.26.3 in /tests/apps/zombies-dockerfile-no-tini +- #8574: @dependabot[bot] chore(deps): bump node from 25-alpine to 26-alpine in /tests/apps/dockerfile-noexpose +- #8575: @dependabot[bot] chore(deps): bump node from 25-alpine to 26-alpine in /tests/apps/dockerfile-procfile-bad +- #8577: @dependabot[bot] chore(deps): bump node from 25-alpine to 26-alpine in /tests/apps/dockerfile-app-json-formations +- #8576: @dependabot[bot] chore(deps): bump node from 25-alpine to 26-alpine in /tests/apps/dockerfile +- #8573: @dependabot[bot] chore(deps): bump node from 25-alpine to 26-alpine in /tests/apps/dockerfile-procfile + +### Dependencies + +- #8579: @josegonzalez Use type prefix for dokku-bot dependency label + +## 0.38.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.1/bootstrap.sh +sudo DOKKU_TAG=v0.38.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #8563: @josegonzalez Source property-functions where fn-plugin-property-* helpers are used +- #8559: @josegonzalez Install cnb pack from github releases instead of ppa +- #8558: @josegonzalez Chown migrated legacy storage entries to dokku +- #8545: @josegonzalez Deflake haproxy bats tests + +### Refactors + +- #8546: @josegonzalez Consolidate nginx.conf.sigil server blocks + +### Documentation + +- #8548: @josegonzalez Use explicit type property in proxy:set examples +- #8547: @josegonzalez Document plugin properties migrated from env vars +- #8544: @josegonzalez Fix reference to when the build plugin was introduced + +### Tests + +- #8565: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.80.0 to 1.81.0 in /tests/apps/gogrpc +- #8568: @dependabot[bot] chore(deps): bump gunicorn from 25.3.0 to 26.0.0 in /tests/apps/multi +- #8572: @dependabot[bot] chore(deps): bump django from 5.2.13 to 5.2.14 in /tests/apps/dockerfile-release +- #8570: @dependabot[bot] chore(deps): bump gunicorn from 25.3.0 to 26.0.0 in /tests/apps/dockerfile-release +- #8567: @dependabot[bot] chore(deps): bump gunicorn from 25.3.0 to 26.0.0 in /tests/apps/python-flask + +### Dependencies + +- #8571: @dependabot[bot] chore(deps): bump traefik from v3.6.15 to v3.7.0 in /plugins/traefik-vhosts +- #8569: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.44 to 2.11.45 in /plugins/scheduler-k3s +- #8561: @dokku-bot chore: bump pack to 0.40.4 +- #8555: @dependabot[bot] chore(deps): bump github.com/mattn/go-isatty from 0.0.20 to 0.0.22 in /plugins/app-json +- #8556: @dependabot[bot] chore(deps): bump github.com/Masterminds/semver/v3 from 3.4.0 to 3.5.0 in /plugins/scheduler-k3s +- #8550: @dokku-bot chore: bump docker-container-healthchecker to 0.15.2 +- #8553: @dokku-bot chore: bump dokku-event-listener to 0.19.1 +- #8552: @dokku-bot chore: bump lambda-builder to 0.9.3 +- #8551: @dokku-bot chore: bump procfile-util to 0.20.7 +- #8554: @dependabot[bot] chore(deps): bump peter-evans/create-pull-request from 7 to 8 + +## 0.38.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.38.0/bootstrap.sh +sudo DOKKU_TAG=v0.38.0 bash bootstrap.sh +``` + +See the [0.38.0 migration guide](/docs/appendices/0.38.0-migration-guide.md) for more information on migrating to 0.38.0. + +### Bug Fixes + +- #8533: @josegonzalez Split env config and image pull secret into separate helm releases +- #8530: @josegonzalez Split multi-flag input in docker-options +- #8528: @josegonzalez Skip retiring images still in use by app containers +- #8525: @josegonzalez Add launcher entrypoint for CNB images on dokku run and cron:run +- #8522: @josegonzalez Only emit keda fallback when a non-cpu/memory trigger exists +- #8515: @josegonzalez Fix vector mount directory config +- #8508: @josegonzalez Preserve all domains when renaming an app +- #8507: @josegonzalez Retire orphaned containers when scaling down + +### New Features + +- #8538: @josegonzalez Add scheduler-aware named storage entries +- #8527: @josegonzalez Accept --global on :report subcommands +- #8524: @josegonzalez Pre-validate custom nginx.conf.sigil during core-post-extract +- #8523: @josegonzalez Support resource limits on the build container +- #8517: @josegonzalez Send SIGTERM to old containers immediately on deploy +- #8516: @josegonzalez Scope docker-options to specific procfile processes +- #8509: @josegonzalez Ship default catch-all site on fresh apt install +- #8506: @josegonzalez Add --format json to git:report and nginx:report +- #8505: @josegonzalez Add git:auth-status to check netrc match +- #8493: @josegonzalez Generate 502 config for apps without web listeners +- #8404: @josegonzalez Upgrade vector chart from 0.42.0 to 0.52.0 +- #8403: @josegonzalez Upgrade ingress-nginx chart from 4.10.0 to 4.15.1 +- #8402: @josegonzalez Upgrade keda to 2.19.0 and keda-add-ons-http to 0.12.2 +- #8259: @josegonzalez Add post-create support for env key in app.json +- #8157: @josegonzalez Add support for specifying buildpacks via app.json +- #8154: @josegonzalez Enable live-restore by default when installing Dokku +- #3697: @josegonzalez Migrate builds plugin to go and track per-build records + +### Refactors + +- #8514: @josegonzalez Migrate docker-options subcommands to go +- #6716: @josegonzalez Move app and global ENV files to consolidated config path + +### Dependencies + +- #8541: @dependabot[bot] chore(deps): bump traefik from v3.6.14 to v3.6.15 in /plugins/traefik-vhosts +- #8537: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.43 to 2.11.44 in /plugins/scheduler-k3s +- #8535: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.39.1 to 1.40.0 in /plugins/common +- #8529: @josegonzalez chore: bump dokku/netrc to v0.11.0 +- #8520: @dependabot[bot] chore(deps): bump packaging from 26.1 to 26.2 in /docs/_build +- #8510: @dependabot[bot] chore(deps): bump packaging from 26.1 to 26.2 in /docs/_build +- #8503: @josegonzalez Bump dependency versions and add daily updater workflow +- #8502: @josegonzalez Bump go version to 1.26.2 +- #8495: @dependabot[bot] chore(deps): bump k8s.io/apimachinery from 0.35.4 to 0.36.0 in /plugins/scheduler-k3s +- #8494: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.9.3 to 0.10.0 in /plugins/openresty-vhosts +- #8490: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.35.4 to 1.36.0 in /plugins/scheduler-k3s + +### Other + +- #8498: @josegonzalez Migrate environment variables to plugin properties + +## 0.37.10 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.10/bootstrap.sh +sudo DOKKU_TAG=v0.37.10 bash bootstrap.sh +``` + +### Tests + +- #8491: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 285 to 287 in /tests/apps/php +- #8478: @josegonzalez chore: label test app dependency updates as type: tests + +### Dependencies + +- #8486: @dependabot[bot] chore(deps): bump click from 8.3.2 to 8.3.3 in /docs/_build +- #8492: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.29.0 to 1.31.0 in /plugins/scheduler-k3s +- #8489: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.42 to 2.11.43 in /plugins/scheduler-k3s +- #8488: @dependabot[bot] chore(deps): bump timberio/vector from 0.54.0-debian to 0.55.0-debian in /plugins/logs +- #8487: @dependabot[bot] chore(deps): bump traefik from v3.6.13 to v3.6.14 in /plugins/traefik-vhosts +- #8483: @dependabot[bot] chore(deps): update markdown requirement from <3.11,>=3.2.1 to >=3.10.2,<3.11 in /docs/_build +- #8485: @dependabot[bot] chore(deps): bump k8s.io/kubectl from 0.35.2 to 0.35.4 in /plugins/scheduler-k3s +- #8484: @dependabot[bot] chore(deps): bump k8s.io/client-go from 0.35.2 to 0.35.4 in /plugins/scheduler-k3s +- #8482: @dependabot[bot] chore(deps): bump ruby from 4.0.2 to 4.0.3 in /tests/apps/dockerfile-entrypoint +- #8481: @dependabot[bot] chore(deps): bump psycopg2-binary from 2.9.11 to 2.9.12 in /tests/apps/dockerfile-release +- #8479: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.23.0 to 0.23.1 in /plugins/scheduler-k3s +- #8480: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.35.3 to 1.35.4 in /plugins/scheduler-k3s + +## 0.37.9 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.9/bootstrap.sh +sudo DOKKU_TAG=v0.37.9 bash bootstrap.sh +``` + +### Dependencies + +- #8473: @dependabot[bot] chore(deps): bump k8s.io/api from 0.35.2 to 0.35.4 in /plugins/scheduler-k3s +- #8474: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.22.5 to 0.23.0 in /plugins/scheduler-k3s +- #8476: @dependabot[bot] chore(deps): bump github.com/go-acme/lego/v4 from 4.25.2 to 4.34.0 in /plugins/scheduler-k3s +- #8475: @dependabot[bot] chore(deps): bump github.com/moby/spdystream from 0.5.0 to 0.5.1 in /plugins/scheduler-k3s + +## 0.37.8 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.8/bootstrap.sh +sudo DOKKU_TAG=v0.37.8 bash bootstrap.sh +``` + +### Bug Fixes + +- #8451: @mykolasolodukha Fix client prepending `--app` for non-app commands + +### New Features + +- #8443: @chemicalkosek Add application/graphql-response+json to nginx gzip_types + +### Dependencies + +- #8470: @dependabot[bot] chore(deps): bump packaging from 26.0 to 26.1 in /docs/_build +- #8471: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.35.0 to 1.35.3 in /plugins/scheduler-k3s +- #8472: @dependabot[bot] chore(deps): bump k8s.io/apimachinery from 0.35.2 to 0.35.3 in /plugins/scheduler-k3s +- #8413: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.24.0 to 1.28.0 in /plugins/scheduler-k3s +- #8425: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.19.3 to 1.20.0 in /plugins/scheduler-k3s +- #8469: @dependabot[bot] chore(deps): bump zipp from 3.23.0 to 3.23.1 in /docs/_build +- #8450: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.13.0 to 3.13.1 in /plugins/cron +- #8465: @dependabot[bot] chore(deps): bump github.com/joho/godotenv from 1.2.0 to 1.5.1 in /plugins/config +- #8463: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.49.0 to 0.50.0 in /plugins/common +- #8449: @dependabot[bot] chore(deps): bump click from 8.3.1 to 8.3.2 in /docs/_build +- #8464: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.20.1 to 3.20.2 in /plugins/scheduler-k3s +- #8458: @dependabot[bot] chore(deps): bump traefik from v3.6.12 to v3.6.13 in /plugins/traefik-vhosts +- #8457: @dependabot[bot] chore(deps): bump golang from 1.26.1 to 1.26.2 in /tests/apps/zombies-dockerfile-no-tini +- #8456: @dependabot[bot] chore(deps): bump golang from 1.26.1 to 1.26.2 in /tests/apps/go-fail-postdeploy +- #8455: @dependabot[bot] chore(deps): bump golang from 1.26.1 to 1.26.2 in /tests/apps/gogrpc +- #8454: @dependabot[bot] chore(deps): bump golang from 1.26.1 to 1.26.2 in /tests/apps/zombies-dockerfile-tini +- #8453: @dependabot[bot] chore(deps): bump golang from 1.26.1 to 1.26.2 in /tests/apps/go-fail-predeploy +- #8452: @dependabot[bot] chore(deps): bump rack-session from 2.1.1 to 2.1.2 in /tests/apps/ruby +- #8459: @dependabot[bot] chore(deps): bump django from 5.2.12 to 5.2.13 in /tests/apps/dockerfile-release +- #8444: @dependabot[bot] chore(deps): bump rack from 3.2.5 to 3.2.6 in /tests/apps/ruby +- #8445: @dependabot[bot] chore(deps): bump github.com/go-jose/go-jose/v4 from 4.1.3 to 4.1.4 in /plugins/scheduler-k3s +- #8447: @dependabot[bot] chore(deps): bump werkzeug from 3.1.7 to 3.1.8 in /tests/apps/python-flask +- #8440: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.21 to 10.21.2 in /docs/_build +- #8441: @dependabot[bot] chore(deps): bump pygments from 2.19.2 to 2.20.0 in /docs/_build +- #8442: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.79.3 to 1.80.0 in /tests/apps/gogrpc +- #8410: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.48.0 to 0.49.0 in /plugins/common +- #8422: @dependabot[bot] chore(deps): bump github.com/fatih/color from 1.18.0 to 1.19.0 in /plugins/common +- #8434: @dependabot[bot] chore(deps): bump gunicorn from 25.2.0 to 25.3.0 in /tests/apps/python-flask +- #8439: @dependabot[bot] chore(deps): bump brace-expansion from 1.1.12 to 1.1.13 in /tests/apps/multi +- #8438: @dependabot[bot] chore(deps): bump path-to-regexp from 8.2.0 to 8.4.0 in /tests/apps/checks-root +- #8437: @dependabot[bot] chore(deps): bump gunicorn from 25.2.0 to 25.3.0 in /tests/apps/dockerfile-release +- #8436: @dependabot[bot] chore(deps): bump traefik from v3.6.11 to v3.6.12 in /plugins/traefik-vhosts +- #8435: @dependabot[bot] chore(deps): bump gunicorn from 25.2.0 to 25.3.0 in /tests/apps/multi +- #8427: @dependabot[bot] chore(deps): bump werkzeug from 3.1.6 to 3.1.7 in /tests/apps/python-flask +- #8428: @dependabot[bot] chore(deps): bump gunicorn from 25.1.0 to 25.2.0 in /tests/apps/multi +- #8429: @dependabot[bot] chore(deps): bump gunicorn from 25.1.0 to 25.2.0 in /tests/apps/python-flask +- #8430: @dependabot[bot] chore(deps): bump djangorestframework from 3.17.0 to 3.17.1 in /tests/apps/dockerfile-release +- #8431: @dependabot[bot] chore(deps): bump gunicorn from 25.1.0 to 25.2.0 in /tests/apps/dockerfile-release +- #8432: @dependabot[bot] chore(deps): bump picomatch from 2.3.1 to 2.3.2 in /tests/apps/multi +- #8423: @dependabot[bot] chore(deps): bump traefik from v3.6.10 to v3.6.11 in /plugins/traefik-vhosts +- #8420: @dependabot[bot] chore(deps): bump mkdocs-material from 9.7.5 to 9.7.6 in /docs/_build +- #8421: @dependabot[bot] chore(deps): bump importlib-metadata from 8.8.0 to 9.0.0 in /docs/_build +- #8424: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.40 to 2.11.41 in /plugins/scheduler-k3s +- #8411: @dependabot[bot] chore(deps): bump lucaslorentz/caddy-docker-proxy from 2.11 to 2.12 in /plugins/caddy-vhosts +- #8412: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.20.0 to 3.20.1 in /plugins/scheduler-k3s +- #8419: @dependabot[bot] chore(deps): bump djangorestframework from 3.16.1 to 3.17.0 in /tests/apps/dockerfile-release +- #8418: @dependabot[bot] chore(deps): bump importlib-metadata from 8.7.1 to 8.8.0 in /docs/_build +- #8417: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.77.0-dev to 1.79.3 in /plugins/scheduler-k3s +- #8416: @dependabot[bot] chore(deps): bump ruby from 4.0.1 to 4.0.2 in /tests/apps/dockerfile-entrypoint +- #8415: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.79.2 to 1.79.3 in /tests/apps/gogrpc +- #8409: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.22.4 to 0.22.5 in /plugins/scheduler-k3s +- #8408: @dependabot[bot] chore(deps): bump github.com/go-resty/resty/v2 from 2.17.1 to 2.17.2 in /plugins/scheduler-k3s +- #8407: @dependabot[bot] chore(deps): bump mkdocs-material from 9.7.4 to 9.7.5 in /docs/_build +- #8406: @dependabot[bot] chore(deps): bump timberio/vector from 0.53.0-debian to 0.54.0-debian in /plugins/logs + +### Other + +- #8468: @josegonzalez chore: bump dependencies in tests/apps/php +- #8467: @josegonzalez chore: upgrade traefik from v2.11.41 to v2.11.42 +- #8466: @josegonzalez chore: bump dependencies in tests/apps/multi + +## 0.37.7 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.7/bootstrap.sh +sudo DOKKU_TAG=v0.37.7 bash bootstrap.sh +``` + +### Bug Fixes + +- #8379: @josegonzalez Call correct function for limiting letsencrypt to certain domains + +### New Features + +- #8405: @josegonzalez Add Debian 13 versions of docker plugin packages +- #8261: @josegonzalez Configure Traefik readiness healthchecks based on app.json config +- #8378: @farmdawgnation Support custom entrypoint for api and dashboard + +### Dependencies + +- #8395: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.12.0 to 3.13.0 in /plugins/cron +- #8401: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.38 to 2.11.40 in /plugins/scheduler-k3s +- #8400: @dependabot[bot] chore(deps): bump github.com/melbahja/goph from 1.4.0 to 1.5.0 in /plugins/common +- #8394: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.47.0 to 0.48.0 in /plugins/common +- #8396: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.19.0 to 0.20.0 in /plugins/common +- #8399: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.19.0 to 0.20.0 in /plugins/scheduler-k3s +- #8398: @dependabot[bot] chore(deps): bump traefik from 3.6.9 to v3.6.10 in /plugins/traefik-vhosts +- #8397: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.19.0 to 0.20.0 in /plugins/scheduler-docker-local +- #8314: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.39.0 to 1.39.1 in /plugins/common +- #8380: @dependabot[bot] chore(deps): bump lucaslorentz/caddy-docker-proxy from 2.10 to 2.11 in /plugins/caddy-vhosts +- #8381: @dependabot[bot] chore(deps): bump k8s.io/kubectl from 0.35.0 to 0.35.2 in /plugins/scheduler-k3s +- #8382: @dependabot[bot] chore(deps): bump docker/login-action from 3 to 4 +- #8383: @dependabot[bot] chore(deps): bump docker/setup-qemu-action from 3 to 4 +- #8384: @dependabot[bot] chore(deps): bump mkdocs-material from 9.7.3 to 9.7.4 in /docs/_build +- #8385: @dependabot[bot] chore(deps): bump django from 5.2.11 to 5.2.12 in /tests/apps/dockerfile-release +- #8387: @dependabot[bot] chore(deps): bump docker/setup-buildx-action from 3 to 4 +- #8388: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.79.1 to 1.79.2 in /tests/apps/gogrpc +- #8389: @dependabot[bot] chore(deps): bump golang from 1.26.0 to 1.26.1 in /tests/apps/go-fail-postdeploy +- #8390: @dependabot[bot] chore(deps): bump golang from 1.26.0 to 1.26.1 in /tests/apps/gogrpc +- #8391: @dependabot[bot] chore(deps): bump golang from 1.26.0 to 1.26.1 in /tests/apps/go-fail-predeploy +- #8392: @dependabot[bot] chore(deps): bump golang from 1.26.0 to 1.26.1 in /tests/apps/zombies-dockerfile-tini +- #8393: @dependabot[bot] chore(deps): bump golang from 1.26.0 to 1.26.1 in /tests/apps/zombies-dockerfile-no-tini +- #8354: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.78.0 to 1.79.1 in /tests/apps/gogrpc +- #8340: @dependabot[bot] chore(deps): bump python from 3.14.2-alpine to 3.14.3-alpine in /docs/_build +- #8353: @dependabot[bot] chore(deps): bump gunicorn from 25.0.2 to 25.1.0 in /tests/apps/multi +- #8357: @dependabot[bot] chore(deps): bump qs from 6.14.1 to 6.14.2 in /tests/apps/checks-root +- #8361: @dependabot[bot] chore(deps): bump flask from 3.1.2 to 3.1.3 in /tests/apps/multi +- #8360: @dependabot[bot] chore(deps): bump rack from 3.2.4 to 3.2.5 in /tests/apps/ruby +- #8332: @dependabot[bot] chore(deps-dev): bump org.apache.maven.plugins:maven-dependency-plugin from 3.9.0 to 3.10.0 in /tests/apps/java +- #8336: @dependabot[bot] chore(deps): bump python from 3.14.2-bookworm to 3.14.3-bookworm in /tests/apps/dockerfile-release +- #8355: @dependabot[bot] chore(deps): bump gunicorn from 25.0.2 to 25.1.0 in /tests/apps/python-flask +- #8356: @dependabot[bot] chore(deps): bump gunicorn from 25.0.2 to 25.1.0 in /tests/apps/dockerfile-release +- #8358: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.20.1 to 10.21 in /docs/_build +- #8371: @dependabot[bot] chore(deps): bump byjg/easy-haproxy from 5.0.0 to 6.0.1 in /plugins/haproxy-vhosts +- #8362: @dependabot[bot] chore(deps): bump flask from 3.1.2 to 3.1.3 in /tests/apps/python-flask +- #8374: @dependabot[bot] chore(deps): bump actions/download-artifact from 7 to 8 +- #8375: @dependabot[bot] chore(deps): bump actions/upload-artifact from 6 to 7 +- #8376: @dependabot[bot] chore(deps): bump phusion/baseimage from noble-1.0.2 to noble-1.0.3 +- #8377: @dependabot[bot] chore(deps): bump whitenoise from 6.11.0 to 6.12.0 in /tests/apps/dockerfile-release +- #8365: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.36 to 2.11.37 in /plugins/scheduler-k3s +- #8367: @dependabot[bot] chore(deps): bump werkzeug from 3.1.5 to 3.1.6 in /tests/apps/python-flask +- #8368: @dependabot[bot] chore(deps): bump dj-database-url from 3.1.0 to 3.1.2 in /tests/apps/dockerfile-release +- #8372: @dependabot[bot] chore(deps): bump mkdocs-material from 9.7.1 to 9.7.3 in /docs/_build +- #8373: @dependabot[bot] chore(deps): bump traefik from 3.6.7 to 3.6.9 in /plugins/traefik-vhosts +- #8351: @dependabot[bot] chore(deps): bump golang from 1.25.6 to 1.26.0 in /tests/apps/zombies-dockerfile-tini +- #8350: @dependabot[bot] chore(deps): bump golang from 1.25.6 to 1.26.0 in /tests/apps/zombies-dockerfile-no-tini +- #8349: @dependabot[bot] chore(deps): bump golang from 1.25.6 to 1.26.0 in /tests/apps/go-fail-postdeploy +- #8348: @dependabot[bot] chore(deps): bump golang from 1.25.6 to 1.26.0 in /tests/apps/gogrpc +- #8347: @dependabot[bot] chore(deps): bump golang from 1.25.6 to 1.26.0 in /tests/apps/go-fail-predeploy +- #8344: @dependabot[bot] chore(deps): bump sigs.k8s.io/kustomize/api from 0.21.0 to 0.21.1 in /plugins/scheduler-k3s +- #8329: @dependabot[bot] chore(deps): bump gunicorn from 25.0.1 to 25.0.2 in /tests/apps/multi +- #8330: @dependabot[bot] chore(deps): bump gunicorn from 25.0.1 to 25.0.2 in /tests/apps/python-flask +- #8331: @dependabot[bot] chore(deps): bump gunicorn from 25.0.1 to 25.0.2 in /tests/apps/dockerfile-release +- #8320: @dependabot[bot] chore(deps): bump django from 5.2.10 to 5.2.11 in /tests/apps/dockerfile-release +- #8321: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.35 to 2.11.36 in /plugins/scheduler-k3s +- #8319: @dependabot[bot] chore(deps): bump gunicorn from 24.1.1 to 25.0.1 in /tests/apps/dockerfile-release +- #8318: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.19.2 to 1.19.3 in /plugins/scheduler-k3s +- #8317: @dependabot[bot] chore(deps): bump gunicorn from 24.1.1 to 25.0.1 in /tests/apps/multi +- #8316: @dependabot[bot] chore(deps): bump gunicorn from 24.1.1 to 25.0.1 in /tests/apps/python-flask + +## 0.37.6 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.6/bootstrap.sh +sudo DOKKU_TAG=v0.37.6 bash bootstrap.sh +``` + +### Bug Fixes + +- #8306: @josegonzalez Ensure we can call storage:report properly + +### Documentation + +- #8289: @josegonzalez Add an architecture document for dokku +- #8288: @josegonzalez Add a readme for the docs folder + +### Dependencies + +- #8312: @dependabot[bot] chore(deps): bump timberio/vector from 0.52.0-debian to 0.53.0-debian in /plugins/logs +- #8311: @dependabot[bot] chore(deps): bump gunicorn from 24.0.0 to 24.1.1 in /tests/apps/dockerfile-release +- #8308: @dependabot[bot] chore(deps): bump gunicorn from 24.0.0 to 24.1.1 in /tests/apps/multi +- #8309: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.20 to 10.20.1 in /docs/_build +- #8307: @dependabot[bot] chore(deps): bump gunicorn from 24.0.0 to 24.1.1 in /tests/apps/python-flask +- #8301: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.19.5 to 3.20.0 in /plugins/scheduler-k3s +- #8299: @dependabot[bot] chore(deps): bump ruby from 4.0.0 to 4.0.1 in /tests/apps/dockerfile-entrypoint +- #8293: @dependabot[bot] chore(deps): bump golang from 1.25.5 to 1.25.6 in /tests/apps/zombies-dockerfile-no-tini +- #8296: @dependabot[bot] chore(deps): bump soupsieve from 2.8.1 to 2.8.3 in /docs/_build +- #8295: @dependabot[bot] chore(deps): bump golang from 1.25.5 to 1.25.6 in /tests/apps/go-fail-postdeploy +- #8294: @dependabot[bot] chore(deps): bump golang from 1.25.5 to 1.25.6 in /tests/apps/gogrpc +- #8291: @dependabot[bot] chore(deps): bump golang from 1.25.5 to 1.25.6 in /tests/apps/zombies-dockerfile-tini +- #8292: @dependabot[bot] chore(deps): bump golang from 1.25.5 to 1.25.6 in /tests/apps/go-fail-predeploy +- #8277: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.34 to 2.11.35 in /plugins/scheduler-k3s +- #8297: @dependabot[bot] chore(deps): bump pyparsing from 3.3.1 to 3.3.2 in /docs/_build +- #8298: @dependabot[bot] chore(deps): bump lodash from 4.17.21 to 4.17.23 in /tests/apps/multi +- #8300: @dependabot[bot] chore(deps): bump packaging from 25.0 to 26.0 in /docs/_build +- #8303: @dependabot[bot] chore(deps): bump gunicorn from 23.0.0 to 24.0.0 in /tests/apps/python-flask +- #8305: @dependabot[bot] chore(deps): bump gunicorn from 23.0.0 to 24.0.0 in /tests/apps/dockerfile-release +- #8304: @dependabot[bot] chore(deps): bump gunicorn from 23.0.0 to 24.0.0 in /tests/apps/multi +- #8280: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.19.4 to 3.19.5 in /plugins/scheduler-k3s +- #8279: @dependabot[bot] chore(deps): bump traefik from 3.6.6 to 3.6.7 in /plugins/traefik-vhosts + +## 0.37.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.5/bootstrap.sh +sudo DOKKU_TAG=v0.37.5 bash bootstrap.sh +``` + +### Bug Fixes + +- #8274: @josegonzalez Delete the tls app chart when the app is deleted +- #8273: @josegonzalez Ensure the destination directory exists when extracting files from a repository +- #8263: @josegonzalez Set correct version for builder-railpack plugin + +### New Features + +- #8268: @josegonzalez Add the ability to log into a registry on a per-app basis +- #8258: @josegonzalez Add support for dns-01 challenge mode when using traefik +- #8262: @josegonzalez Use certificates imported by certs plugin when deploying via scheduler-k3s +- #8266: @josegonzalez Add a method to force build when re-using an image with git:from-image +- #8265: @josegonzalez Add the ability to skip setting the deploy-branch when running git:sync + +### Refactors + +- #8264: @josegonzalez Rewrite the storage plugin in golang + +### Dependencies + +- #8270: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.38.3 to 1.39.0 in /plugins/common +- #8267: @dependabot[bot] chore(deps): bump werkzeug from 3.1.4 to 3.1.5 in /tests/apps/python-flask +- #8260: @dependabot[bot] chore(deps): bump django from 5.2.9 to 5.2.10 in /tests/apps/dockerfile-release +- #8256: @dependabot[bot] chore(deps): bump luizm/action-sh-checker from 0.9.0 to 0.10.0 +- #8257: @dependabot[bot] chore(deps): bump dj-database-url from 3.0.1 to 3.1.0 in /tests/apps/dockerfile-release + +## 0.37.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.4/bootstrap.sh +sudo DOKKU_TAG=v0.37.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #8168: @josegonzalez Install logrotate in the container + +### Tests + +- #8254: @josegonzalez Add a test to prove cron:run commands work as expected +- #8255: @josegonzalez Update test ruby app + +### Dependencies + +- #8252: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.19.1 to 10.20 in /docs/_build +- #8253: @dependabot[bot] chore(deps): bump qs from 6.14.0 to 6.14.1 in /tests/apps/checks-root +- #8249: @dependabot[bot] chore(deps): bump traefik from 3.6.5 to 3.6.6 in /plugins/traefik-vhosts +- #8250: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.33 to 2.11.34 in /plugins/scheduler-k3s +- #8247: @dependabot[bot] chore(deps): bump ruby from 3.4.8 to 4.0.0 in /tests/apps/dockerfile-entrypoint + +### Other + +- #8248: @osbre Add `proxy-keepalive` to Nginx properties + +## 0.37.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.3/bootstrap.sh +sudo DOKKU_TAG=v0.37.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #8199: @josegonzalez Pass global build args as array when building nixpacks apps +- #8236: @josegonzalez Ensure the /etc/docker exists when interacting with it via postinst + +### Documentation + +- #8229: @kleutzinger Fix command syntax for removing docker options +- #8219: @deanmarano Add deanmarano to community plugins list +- #8205: @deanmarano Add DNS plugin to community plugins list + +### Tests + +- #8246: @josegonzalez Add tests to prove that Procfile tasks all work for every builder + +### Dependencies + +- #8243: @dependabot[bot] chore(deps): bump k8s.io/kubectl from 0.34.2 to 0.35.0 in /plugins/scheduler-k3s +- #8245: @dependabot[bot] chore(deps): bump github.com/go-resty/resty/v2 from 2.17.0 to 2.17.1 in /plugins/scheduler-k3s +- #8244: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.34.2 to 1.35.0 in /plugins/scheduler-k3s +- #8233: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.38.2 to 1.38.3 in /plugins/common +- #8241: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.32 to 2.11.33 in /plugins/scheduler-k3s +- #8240: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.19.1 to 1.19.2 in /plugins/scheduler-k3s +- #8239: @dependabot[bot] chore(deps): bump pyparsing from 3.2.5 to 3.3.1 in /docs/_build +- #8238: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.77.0 to 1.78.0 in /tests/apps/gogrpc +- #8237: @dependabot[bot] chore(deps): bump github.com/kedacore/keda/v2 from 2.18.2 to 2.18.3 in /plugins/scheduler-k3s +- #8235: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.19.2 to 3.19.4 in /plugins/scheduler-k3s +- #8232: @dependabot[bot] chore(deps): bump importlib-metadata from 8.7.0 to 8.7.1 in /docs/_build +- #8234: @dependabot[bot] chore(deps): bump k8s.io/client-go from 0.34.2 to 0.35.0 in /plugins/scheduler-k3s +- #8231: @dependabot[bot] chore(deps): bump github.com/expr-lang/expr from 1.17.6 to 1.17.7 in /plugins/scheduler-k3s +- #8208: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.45.0 to 0.46.0 in /plugins/common +- #8201: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.18.0 to 0.19.0 in /plugins/common +- #8214: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.10 to 1.36.11 in /tests/apps/gogrpc +- #8216: @dependabot[bot] chore(deps): bump actions/download-artifact from 6 to 7 +- #8217: @dependabot[bot] chore(deps): bump actions/upload-artifact from 5 to 6 +- #8228: @dependabot[bot] chore(deps): bump soupsieve from 2.8 to 2.8.1 in /docs/_build +- #8226: @dependabot[bot] chore(deps): bump ruby from 3.4.7 to 3.4.8 in /tests/apps/dockerfile-entrypoint +- #8227: @dependabot[bot] chore(deps): bump mkdocs-material from 9.7.0 to 9.7.1 in /docs/_build +- #8224: @dependabot[bot] chore(deps): bump traefik from 3.6.4 to 3.6.5 in /plugins/traefik-vhosts +- #8225: @dependabot[bot] chore(deps): bump timberio/vector from 0.51.1-debian to 0.52.0-debian in /plugins/logs +- #8220: @dependabot[bot] chore(deps): bump python from 3.14.1-bookworm to 3.14.2-bookworm in /tests/apps/dockerfile-release +- #8221: @dependabot[bot] chore(deps): bump python from 3.14.1-alpine to 3.14.2-alpine in /docs/_build +- #8218: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.19 to 10.19.1 in /docs/_build +- #8215: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.18 to 10.19 in /docs/_build +- #8209: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.38.2 to 1.38.3 in /plugins/config +- #8210: @dependabot[bot] chore(deps): bump github.com/kedacore/keda/v2 from 2.18.1 to 2.18.2 in /plugins/scheduler-k3s +- #8211: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.22.3 to 0.22.4 in /plugins/scheduler-k3s +- #8200: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.17.2 to 10.18 in /docs/_build +- #8202: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.18.0 to 0.19.0 in /plugins/scheduler-docker-local +- #8203: @dependabot[bot] chore(deps): bump traefik from 3.6.2 to 3.6.4 in /plugins/traefik-vhosts +- #8204: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.18.0 to 0.19.0 in /plugins/scheduler-k3s + +## 0.37.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.2/bootstrap.sh +sudo DOKKU_TAG=v0.37.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #8198: @josegonzalez Ensure we continue on with logic if the error returned from fetching all apps is NoAppsExist + +### Dependencies + +- #8196: @dependabot[bot] chore(deps): bump byjg/easy-haproxy from 4.6.0 to 5.0.0 in /plugins/haproxy-vhosts +- #8197: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.31 to 2.11.32 in /plugins/scheduler-k3s + +## 0.37.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.1/bootstrap.sh +sudo DOKKU_TAG=v0.37.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #8186: @josegonzalez Update url to download golang +- #8155: @josegonzalez Sanitize vector sink values in report output +- #8156: @josegonzalez Do not start nginx if there are no apps with nginx as a proxy +- #8152: @josegonzalez Ensure keda usage does not cause jank in scaling deployments + +### New Features + +- #8154: @josegonzalez Enable live-restore by default when installing Dokku +- #8151: @josegonzalez Allow exposing non-web processes as kubernetes services + +### Refactors + +- #8149: @josegonzalez Remove nginx checks for functionality that always exists + +### Documentation + +- #8170: @znz Fix version in migration docs +- #8169: @josegonzalez Update logging in doc generation to enhance doc generation debugging +- #8153: @josegonzalez Fix reference to letsencrypt-server property +- #8150: @josegonzalez Clarify system requirements for k3s usage +- #8148: @josegonzalez Clarify plugin installation documentation + +### Dependencies + +- #8192: @dependabot[bot] chore(deps): bump golang from 1.25.4 to 1.25.5 in /tests/apps/go-fail-predeploy +- #8191: @dependabot[bot] chore(deps): bump golang from 1.25.4 to 1.25.5 in /tests/apps/go-fail-postdeploy +- #8189: @dependabot[bot] chore(deps): bump golang from 1.25.4 to 1.25.5 in /tests/apps/gogrpc +- #8193: @dependabot[bot] chore(deps): bump python from 3.14.0-alpine to 3.14.1-alpine in /docs/_build +- #8190: @dependabot[bot] chore(deps): bump golang from 1.25.4 to 1.25.5 in /tests/apps/zombies-dockerfile-no-tini +- #8188: @dependabot[bot] chore(deps): bump golang from 1.25.4 to 1.25.5 in /tests/apps/zombies-dockerfile-tini +- #8194: @dependabot[bot] chore(deps): bump python from 3.14.0-bookworm to 3.14.1-bookworm in /tests/apps/dockerfile-release +- #8195: @dependabot[bot] chore(deps): bump django from 5.2.8 to 5.2.9 in /tests/apps/dockerfile-release +- #8177: @dependabot[bot] chore(deps): bump express from 5.1.0 to 5.2.1 in /tests/apps/nodejs-express-noprocfile +- #8178: @dependabot[bot] chore(deps): bump express from 5.1.0 to 5.2.1 in /tests/apps/dockerfile-procfile-bad +- #8179: @dependabot[bot] chore(deps): bump express from 5.1.0 to 5.2.1 in /tests/apps/nodejs-express +- #8180: @dependabot[bot] chore(deps): bump express from 5.1.0 to 5.2.1 in /tests/apps/dockerfile-procfile +- #8181: @dependabot[bot] chore(deps): bump express from 5.1.0 to 5.2.1 in /tests/apps/gitsubmodules +- #8183: @dependabot[bot] chore(deps): bump express from 5.1.0 to 5.2.1 in /tests/apps/checks-root +- #8182: @dependabot[bot] chore(deps): bump express from 5.1.0 to 5.2.1 in /tests/apps/nodejs-express-noappjson +- #8184: @dependabot[bot] chore(deps): bump express from 5.1.0 to 5.2.1 in /tests/apps/dockerfile-app-json-formations +- #8172: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.14.2 to 4.14.3 in /docs/_build +- #8173: @dependabot[bot] chore(deps): bump werkzeug from 3.1.3 to 3.1.4 in /tests/apps/python-flask +- #8164: @dependabot[bot] chore(deps): bump body-parser from 2.2.0 to 2.2.1 in /tests/apps/checks-root +- #8165: @dependabot[bot] chore(deps): bump github.com/go-resty/resty/v2 from 2.16.5 to 2.17.0 in /plugins/scheduler-k3s +- #8167: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.17.1 to 10.17.2 in /docs/_build +- #8162: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.30 to 2.11.31 in /plugins/scheduler-k3s +- #8161: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.23.0 to 1.24.0 in /plugins/scheduler-k3s +- #8159: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.19.0 to 3.19.2 in /plugins/scheduler-k3s +- #8158: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.34.1 to 1.34.2 in /plugins/scheduler-k3s +- #8143: @dependabot[bot] chore(deps): bump actions/checkout from 5 to 6 +- #8145: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.22.1 to 0.22.3 in /plugins/scheduler-k3s +- #8146: @dependabot[bot] chore(deps): bump k8s.io/kubectl from 0.34.1 to 0.34.2 in /plugins/scheduler-k3s +- #8144: @dependabot[bot] chore(deps): bump traefik from 3.6.1 to 3.6.2 in /plugins/traefik-vhosts + +## 0.37.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.37.0/bootstrap.sh +sudo DOKKU_TAG=v0.37.0 bash bootstrap.sh +``` + +See the [0.37.0 migration guide](/docs/appendices/0.37.0-migration-guide.md) for more information on migrating to 0.37.0. + +### Backwards Compatibility Breaks + +- #8140: @josegonzalez Rename the scheduler-k3s:cluster-* commands to use a : instead of - for a delimiter +- #7982: @josegonzalez Remove references to pack being experimental + +### Bug Fixes + +- #8136: @josegonzalez Do not reset manually set port mappings when upgrading dokku +- #8114: @josegonzalez Ensure we can execute run commands when exec is executed as part of an entrypoint +- #8109: @josegonzalez Allow skipping the install trigger when installing a plugin +- #8104: @josegonzalez Drop deprecated aufs check +- #8100: @othercorey Add process-type label for run with procfile cmd + +### New Features + +- #8139: @josegonzalez Add aliases for select builder and scheduler plugins +- #8138: @josegonzalez Turn on buildpack trace mode when dokku trace is on +- #8137: @josegonzalez Add the ability to create k3s node profiles +- #8135: @josegonzalez Add support for git remotes with port specified inline +- #8112: @josegonzalez Implement cron and one-off run task TTLs +- #8106: @josegonzalez Support inline comments for app.json +- #8105: @josegonzalez Allow specifying kubelet-args when adding a new node to the cluster +- #8085: @josegonzalez Implement config:import command +- #8004: @Tashows Add buildpacks:detect subcommand +- #7991: @josegonzalez Add ability to pause/restart cron jobs +- #7989: @josegonzalez Add support to prevent overlapping execution of cron tasks +- #7986: @josegonzalez Add support for specifying CNB buildpacks via buildpacks command +- #7956: @josegonzalez Add a Railpack builder to Dokku +- #7608: @dragonhunt02 Add commands to proxy implementations for managing labels + +### Refactors + +- #8101: @josegonzalez Filter build docker-options correctly for each builder + +### Documentation + +- #8103: @josegonzalez Correct the release version for railpacks support + +### Dependencies + +- #8130: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0 in /plugins/common +- #8110: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.76.0 to 1.77.0 in /tests/apps/gogrpc +- #8108: @dependabot[bot] chore(deps): bump js-yaml from 3.14.1 to 3.14.2 in /tests/apps/multi +- #8107: @dependabot[bot] chore(deps): bump click from 8.3.0 to 8.3.1 in /docs/_build +- #8098: @dependabot[bot] chore(deps): bump traefik from 3.5.4 to 3.6.1 in /plugins/traefik-vhosts +- #8097: @dependabot[bot] chore(deps): bump timberio/vector from 0.51.0-debian to 0.51.1-debian in /plugins/logs +- #8094: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.16.1 to 10.17.1 in /docs/_build +- #8092: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.23 to 9.7.0 in /docs/_build +- #8090: @dependabot[bot] chore(deps): bump sigs.k8s.io/kustomize/api from 0.20.1 to 0.21.0 in /plugins/scheduler-k3s + +## 0.36.11 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.11/bootstrap.sh +sudo DOKKU_TAG=v0.36.11 bash bootstrap.sh +``` + +### Bug Fixes + +- #8084: @josegonzalez Use the correct case for AS in Dockerfile +- #8083: @josegonzalez Use correct variable for tasks +- #8081: @josegonzalez Error out if the appname or app.json is missing when retrieving cron entries + +### Refactors + +- #8082: @josegonzalez Standardize naming in cron code + +### Dependencies + +- #8073: @dependabot[bot] chore(deps): bump timberio/vector from 0.50.0-debian to 0.51.0-debian in /plugins/logs +- #8080: @dependabot[bot] chore(deps): bump github.com/containerd/containerd from 1.7.28 to 1.7.29 in /plugins/scheduler-k3s +- #8079: @dependabot[bot] chore(deps): bump golang from 1.25.3 to 1.25.4 in /tests/apps/zombies-dockerfile-tini +- #8075: @dependabot[bot] chore(deps): bump golang from 1.25.3 to 1.25.4 in /tests/apps/go-fail-postdeploy +- #8078: @dependabot[bot] chore(deps): bump golang from 1.25.3 to 1.25.4 in /tests/apps/go-fail-predeploy +- #8077: @dependabot[bot] chore(deps): bump golang from 1.25.3 to 1.25.4 in /tests/apps/zombies-dockerfile-no-tini +- #8076: @dependabot[bot] chore(deps): bump golang from 1.25.3 to 1.25.4 in /tests/apps/gogrpc +- #8072: @dependabot[bot] chore(deps): update markdown requirement from <3.10,>=3.2.1 to >=3.2.1,<3.11 in /docs/_build +- #8074: @dependabot[bot] chore(deps): bump django from 5.2.7 to 5.2.8 in /tests/apps/dockerfile-release +- #8071: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.22 to 9.6.23 in /docs/_build +- #8068: @dependabot[bot] chore(deps): bump traefik from 3.5.3 to 3.5.4 in /plugins/traefik-vhosts +- #8069: @dependabot[bot] chore(deps): bump github.com/kedacore/keda/v2 from 2.18.0 to 2.18.1 in /plugins/scheduler-k3s +- #8066: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.29 to 2.11.30 in /plugins/scheduler-k3s + +## 0.36.10 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.10/bootstrap.sh +sudo DOKKU_TAG=v0.36.10 bash bootstrap.sh +``` + +### Bug Fixes + +- #8063: @othercorey Use -t instead of deprecated --time in stop commands +- #8058: @josegonzalez Ensure the pre-receive hook and URLS files are correct when cloning or renaming apps +- #8059: @josegonzalez Expand variables for release tasks +- #8060: @josegonzalez Use updated http2 directive for setting http2 support + +### Documentation + +- #8051: @othercorey Fix "its" in Caddy SSL configuration documentation + +### Dependencies + +- #8065: @dependabot[bot] chore(deps): bump actions/upload-artifact from 4 to 5 +- #8064: @dependabot[bot] chore(deps): bump actions/download-artifact from 5 to 6 +- #8057: @dependabot[bot] chore(deps): bump node from 24-alpine to 25-alpine in /tests/apps/dockerfile-procfile-bad +- #8056: @dependabot[bot] chore(deps): bump node from 24-alpine to 25-alpine in /tests/apps/dockerfile-app-json-formations +- #8055: @dependabot[bot] chore(deps): bump node from 24-alpine to 25-alpine in /tests/apps/dockerfile-procfile +- #8054: @dependabot[bot] chore(deps): bump node from 24-alpine to 25-alpine in /tests/apps/dockerfile +- #8053: @dependabot[bot] chore(deps): bump node from 24-alpine to 25-alpine in /tests/apps/dockerfile-noexpose + +## 0.36.9 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.9/bootstrap.sh +sudo DOKKU_TAG=v0.36.9 bash bootstrap.sh +``` + +### Bug Fixes + +- #8032: @josegonzalez Set correct labels for tests/apps/dockerfile-release pip updates + +### New Features + +- #8047: @josegonzalez Add ability to disable letsencrypt for a given application or globally + +### Dependencies + +- #8049: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.19.0 to 1.19.1 in /plugins/scheduler-k3s +- #8048: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.21 to 9.6.22 in /docs/_build +- #8042: @dependabot[bot] chore(deps): bump django from 5.1.13 to 5.2.7 in /tests/apps/dockerfile-release +- #8043: @dependabot[bot] chore(deps): bump whitenoise from 6.2.0 to 6.11.0 in /tests/apps/dockerfile-release +- #8044: @dependabot[bot] chore(deps): bump djangorestframework from 3.15.2 to 3.16.1 in /tests/apps/dockerfile-release +- #8045: @dependabot[bot] chore(deps): bump psycopg2-binary from 2.9.5 to 2.9.11 in /tests/apps/dockerfile-release +- #8046: @dependabot[bot] chore(deps): bump dj-database-url from 1.0.0 to 3.0.1 in /tests/apps/dockerfile-release +- #8041: @dependabot[bot] chore(deps): bump golang from 1.25.2 to 1.25.3 in /tests/apps/gogrpc +- #8040: @dependabot[bot] chore(deps): bump golang from 1.25.2 to 1.25.3 in /tests/apps/go-fail-predeploy +- #8039: @dependabot[bot] chore(deps): bump golang from 1.25.2 to 1.25.3 in /tests/apps/zombies-dockerfile-no-tini +- #8037: @dependabot[bot] chore(deps): bump golang from 1.25.2 to 1.25.3 in /tests/apps/go-fail-postdeploy +- #8038: @dependabot[bot] chore(deps): bump golang from 1.25.2 to 1.25.3 in /tests/apps/zombies-dockerfile-tini +- #8036: @dependabot[bot] chore(deps): bump actions/setup-node from 5 to 6 +- #8033: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.12.1 to 0.13.0 in /plugins/scheduler-k3s +- #8034: @dependabot[bot] chore(deps): bump github.com/kedacore/keda/v2 from 2.17.1-0.20250708210620-a239d2459a35 to 2.18.0 in /plugins/scheduler-k3s + +## 0.36.8 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.8/bootstrap.sh +sudo DOKKU_TAG=v0.36.8 bash bootstrap.sh +``` + +### Bug Fixes + +- #8031: @josegonzalez Respect the k3s master node version when adding a new worker to the k3s cluster +- #7990: @josegonzalez Correct parsing of the --container-id flag for the enter command + +### Documentation + +- #8013: @othercorey Change command to add SSH key with sudo +- #7998: @bakatz Remove incorrect documentation about --envfile flag for config:export + +### Dependencies + +- #8028: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.42.0 to 0.43.0 in /plugins/common +- #8015: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.18.2 to 1.19.0 in /plugins/scheduler-k3s +- #8029: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.12.1 to 0.13.0 in /plugins/ps +- #8011: @dependabot[bot] chore(deps): bump org.apache.maven.plugins:maven-dependency-plugin from 3.8.1 to 3.9.0 in /tests/apps/java +- #8012: @dependabot[bot] chore(deps): bump click from 8.1.8 to 8.3.0 in /docs/_build +- #8014: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.75.1 to 1.76.0 in /tests/apps/gogrpc +- #8018: @dependabot[bot] chore(deps): bump github/codeql-action from 3 to 4 +- #8019: @dependabot[bot] chore(deps): bump golang from 1.25.1 to 1.25.2 in /tests/apps/zombies-dockerfile-no-tini +- #8020: @dependabot[bot] chore(deps): bump golang from 1.25.1 to 1.25.2 in /tests/apps/go-fail-predeploy +- #8021: @dependabot[bot] chore(deps): bump python from 3.13.7-bookworm to 3.14.0-bookworm in /tests/apps/dockerfile-release +- #8022: @dependabot[bot] chore(deps): bump golang from 1.25.1 to 1.25.2 in /tests/apps/gogrpc +- #8024: @dependabot[bot] chore(deps): bump golang from 1.25.1 to 1.25.2 in /tests/apps/zombies-dockerfile-tini +- #8023: @dependabot[bot] chore(deps): bump golang from 1.25.1 to 1.25.2 in /tests/apps/go-fail-postdeploy +- #8025: @dependabot[bot] chore(deps): bump python from 3.13.7-alpine to 3.14.0-alpine in /docs/_build +- #8026: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.22.0 to 1.23.0 in /plugins/scheduler-k3s +- #8027: @dependabot[bot] chore(deps): bump ruby from 3.4.6 to 3.4.7 in /tests/apps/dockerfile-entrypoint +- #8030: @dependabot[bot] chore(deps): bump rack from 2.2.19 to 2.2.20 in /tests/apps/ruby +- #8016: @dependabot[bot] chore(deps): bump rack from 2.2.18 to 2.2.19 in /tests/apps/ruby +- #8009: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.9 to 1.36.10 in /tests/apps/gogrpc +- #7994: @dependabot[bot] chore(deps): bump timberio/vector from 0.49.0-debian to 0.50.0-debian in /plugins/logs +- #8006: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.20 to 9.6.21 in /docs/_build +- #8003: @dependabot[bot] chore(deps): bump traefik from 3.5.2 to 3.5.3 in /plugins/traefik-vhosts +- #7996: @dependabot[bot] chore(deps): bump pyyaml from 6.0.2 to 6.0.3 in /docs/_build +- #8000: @dependabot[bot] chore(deps): bump markupsafe from 3.0.2 to 3.0.3 in /docs/_build +- #8001: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.13.5 to 4.14.2 in /docs/_build +- #8002: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.22.0 to 0.22.1 in /plugins/scheduler-k3s +- #7995: @dependabot[bot] chore(deps): bump rack from 2.2.17 to 2.2.18 in /tests/apps/ruby +- #7992: @dependabot[bot] chore(deps): bump hadolint/hadolint-action from 3.2.0 to 3.3.0 +- #7993: @dependabot[bot] chore(deps): bump pyparsing from 3.2.4 to 3.2.5 in /docs/_build +- #7987: @dependabot[bot] chore(deps): bump ruby from 3.4.5 to 3.4.6 in /tests/apps/dockerfile-entrypoint +- #7983: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.34.0 to 1.34.1 in /plugins/scheduler-k3s +- #7984: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.19 to 9.6.20 in /docs/_build +- #7985: @dependabot[bot] chore(deps): bump pyparsing from 3.2.3 to 3.2.4 in /docs/_build + +### Other + +- #8008: @dependabot[bot] chore(deps): bump django from 5.1.12 to 5.1.13 in /tests/apps/dockerfile-release + +## 0.36.7 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.7/bootstrap.sh +sudo DOKKU_TAG=v0.36.7 bash bootstrap.sh +``` + +### Bug Fixes + +- #7981: @josegonzalez Only install software-properties-common on Ubuntu and older Debian installations +- #7979: @josegonzalez Skip invalid apps when listing applications +- #7977: @kminek Remove server_tokens directive from main nginx config if present (comâ€Ļ +- #7974: @josegonzalez Ensure lsb_release is available on debian systems + +### New Features + +- #7978: @josegonzalez Add ability to list global cron tasks +- #7975: @josegonzalez Create SecurityContext for k3s scheduler from docker-options + +### Dependencies + +- #7972: @dependabot[bot] chore(deps): bump k8s.io/kubectl from 0.34.0 to 0.34.1 in /plugins/scheduler-k3s +- #7973: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.18.6 to 3.19.0 in /plugins/scheduler-k3s +- #7971: @dependabot[bot] chore(deps): bump k8s.io/client-go from 0.34.0 to 0.34.1 in /plugins/scheduler-k3s +- #7970: @dependabot[bot] chore(deps): bump k8s.io/api from 0.34.0 to 0.34.1 in /plugins/scheduler-k3s +- #7966: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.19.0 to 1.22.0 in /plugins/scheduler-k3s +- #7963: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.75.0 to 1.75.1 in /tests/apps/gogrpc +- #7964: @dependabot[bot] chore(deps): bump traefik from 3.5.1 to 3.5.2 in /plugins/traefik-vhosts +- #7965: @dependabot[bot] chore(deps): bump k8s.io/apimachinery from 0.34.0 to 0.34.1 in /plugins/scheduler-k3s +- #7961: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.41.0 to 0.42.0 in /plugins/common +- #7960: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.8 to 1.36.9 in /tests/apps/gogrpc + +### Other + +- #7968: @josegonzalez chore: bump herokuish version from 0.11.0 to 0.11.3 +- #7962: @dependabot[bot] chore(deps): bump django from 5.1.10 to 5.1.12 in /tests/apps/dockerfile-release + +## 0.36.6 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.6/bootstrap.sh +sudo DOKKU_TAG=v0.36.6 bash bootstrap.sh +``` + +### Bug Fixes + +- #7955: @josegonzalez Ensure that the nixpacks bin is installed in /usr/bin/nixpacks in Docker image +- #7953: @josegonzalez Set correct label for nixpacks builder +- #7942: @josegonzalez Ensure the code is copied over to the new app on clone or rebase +- #7941: @josegonzalez Do not generate an https url when there is no SSL certificate +- #7940: @josegonzalez Silence openresty extract warnings during the deploy process + +### Tests + +- #7954: @josegonzalez Respect DOKKU_DOMAIN when deploying in tests +- #7943: @josegonzalez Remove unused test app +- #7939: @josegonzalez Add test for ensuring run commands work with pack-based builds + +### Dependencies + +- #7958: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.18.5 to 3.18.6 in /plugins/scheduler-k3s +- #7952: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.16.0 to 0.17.0 in /plugins/common +- #7951: @dependabot[bot] chore(deps): bump golang from 1.25.0 to 1.25.1 in /tests/apps/gogrpc +- #7950: @dependabot[bot] chore(deps): bump golang from 1.25.0 to 1.25.1 in /tests/apps/go-fail-postdeploy +- #7949: @dependabot[bot] chore(deps): bump golang from 1.25.0 to 1.25.1 in /tests/apps/zombies-dockerfile-no-tini +- #7947: @dependabot[bot] chore(deps): bump golang from 1.25.0 to 1.25.1 in /tests/apps/zombies-dockerfile-tini +- #7945: @dependabot[bot] chore(deps): bump golang from 1.25.0 to 1.25.1 in /tests/apps/go-fail-predeploy +- #7946: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.18 to 9.6.19 in /docs/_build + +## 0.36.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.5/bootstrap.sh +sudo DOKKU_TAG=v0.36.5 bash bootstrap.sh +``` + +### New Features + +- #7937: @josegonzalez Add support for Debian Trixie + +## 0.36.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.4/bootstrap.sh +sudo DOKKU_TAG=v0.36.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #7914: @josegonzalez Fix nginx configuration building in k3s scheduler + +### Dependencies + +- #7935: @dependabot[bot] chore(deps): update markdown requirement from <3.9,>=3.2.1 to >=3.2.1,<3.10 in /docs/_build +- #7936: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.21.2 to 0.22.0 in /plugins/scheduler-k3s +- #7931: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.28 to 2.11.29 in /plugins/scheduler-k3s +- #7932: @dependabot[bot] chore(deps): bump actions/setup-python from 5 to 6 +- #7933: @dependabot[bot] chore(deps): bump actions/setup-node from 4 to 5 +- #7934: @dependabot[bot] chore(deps): bump hadolint/hadolint-action from 3.1.0 to 3.2.0 +- #7920: @dependabot[bot] chore(deps): bump github.com/spf13/pflag from 1.0.9 to 1.0.10 in /plugins/common +- #7887: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.38.0 to 1.38.2 in /plugins/common +- #7847: @dependabot[bot] chore(deps): bump timberio/vector from 0.48.0-debian to 0.49.0-debian in /plugins/logs +- #7890: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.33.2 to 1.34.0 in /plugins/scheduler-k3s +- #7881: @dependabot[bot] chore(deps): bump byjg/easy-haproxy from 4.5.0 to 4.6.0 in /plugins/haproxy-vhosts +- #7880: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.13.4 to 4.13.5 in /docs/_build +- #7888: @dependabot[bot] chore(deps): bump soupsieve from 2.7 to 2.8 in /docs/_build +- #7902: @dependabot[bot] chore(deps): bump github.com/spf13/pflag from 1.0.7 to 1.0.9 in /plugins/common +- #7891: @dependabot[bot] chore(deps): bump k8s.io/kubectl from 0.33.3 to 0.34.0 in /plugins/scheduler-k3s +- #7895: @dependabot[bot] chore(deps): bump traefik from 3.5.0 to 3.5.1 in /plugins/traefik-vhosts + +## 0.36.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.3/bootstrap.sh +sudo DOKKU_TAG=v0.36.3 bash bootstrap.sh +``` + +### New Features + +- #7878: @josegonzalez Allow specifying base64-encoded values in vector-sink DSN urls + +### Dependencies + +- #7873: @dependabot[bot] chore(deps): bump github.com/go-openapi/jsonpointer from 0.21.1 to 0.21.2 in /plugins/scheduler-k3s +- #7874: @dependabot[bot] chore(deps): bump k8s.io/api from 0.33.3 to 0.33.4 in /plugins/scheduler-k3s +- #7875: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.17 to 9.6.18 in /docs/_build + +## 0.36.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.2/bootstrap.sh +sudo DOKKU_TAG=v0.36.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #7872: @josegonzalez Correct issues when scaling non-web processes for the scheduler-k3s plugin + +### Refactors + +- #7871: @josegonzalez Remove public exposure of DOKKU_APP_TYPE in favor of builder detected property + +### Dependencies + +- #7868: @dependabot[bot] chore(deps): bump grunt-cli from 1.4.3 to 1.5.0 in /tests/apps/multi +- #7869: @dependabot[bot] chore(deps): bump bower from 1.8.12 to 1.8.14 in /tests/apps/multi +- #7870: @dependabot[bot] chore(deps): bump tmp from 0.2.4 to 0.2.5 in /tests/apps/multi +- #7867: @dependabot[bot] chore(deps): bump grunt from 1.5.3 to 1.6.1 in /tests/apps/multi +- #7866: @dependabot[bot] chore(deps): bump github.com/fluxcd/pkg/kustomize from 1.18.0 to 1.19.0 in /plugins/scheduler-k3s +- #7862: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.7 to 1.36.8 in /tests/apps/gogrpc + +### Other + +- #7864: @josegonzalez chore(deps): add dependabot entries for plugins/scheduler-k3s and tests/apps/multi + +## 0.36.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.1/bootstrap.sh +sudo DOKKU_TAG=v0.36.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #7846: @josegonzalez Correct issue in parsing escaped plus signs in vector sink values +- #7841: @leksyib14 Fix handling of equals sign in vector sink values +- #7844: @josegonzalez Do not change permissions on config directory symlinks + +### Tests + +- #7848: @josegonzalez Ensure tests properly fetch data from json output +- #7845: @josegonzalez Use the official github actions arm64 runner + +### Dependencies + +- #7861: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.74.2 to 1.75.0 in /tests/apps/gogrpc +- #7860: @dependabot[bot] chore(deps): bump flask from 3.1.1 to 3.1.2 in /tests/apps/python-flask +- #7859: @dependabot[bot] chore(deps): bump flask from 3.1.1 to 3.1.2 in /tests/apps/multi +- #7856: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.16 to 9.6.17 in /docs/_build +- #7857: @dependabot[bot] chore(deps): bump python from 3.13.6-alpine to 3.13.7-alpine in /docs/_build +- #7858: @dependabot[bot] chore(deps): bump python from 3.13.6-bookworm to 3.13.7-bookworm in /tests/apps/dockerfile-release +- #7855: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.25 to 11.0.26 in /tests/apps/java +- #7849: @dependabot[bot] chore(deps): bump golang from 1.24.6 to 1.25.0 in /tests/apps/zombies-dockerfile-tini +- #7850: @dependabot[bot] chore(deps): bump golang from 1.24.6 to 1.25.0 in /tests/apps/go-fail-predeploy +- #7851: @dependabot[bot] chore(deps): bump golang from 1.24.6 to 1.25.0 in /tests/apps/go-fail-postdeploy +- #7852: @dependabot[bot] chore(deps): bump golang from 1.24.6 to 1.25.0 in /tests/apps/gogrpc +- #7853: @dependabot[bot] chore(deps): bump golang from 1.24.6 to 1.25.0 in /tests/apps/zombies-dockerfile-no-tini +- #7842: @dependabot[bot] chore(deps): bump actions/checkout from 4 to 5 +- #7821: @dependabot[bot] chore(deps): bump traefik from 3.4.4 to 3.5.0 in /plugins/traefik-vhosts +- #7843: @josegonzalez Bump go modules +- #7824: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.15 to 9.6.16 in /docs/_build +- #7823: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.16 to 10.16.1 in /docs/_build +- #7831: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.6 to 1.36.7 in /tests/apps/gogrpc +- #7839: @dependabot[bot] chore(deps): bump python from 3.13.5-bookworm to 3.13.6-bookworm in /tests/apps/dockerfile-release +- #7838: @dependabot[bot] chore(deps): bump python from 3.13.5-alpine to 3.13.6-alpine in /docs/_build +- #7837: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.40.0 to 0.41.0 in /plugins/common +- #7836: @dependabot[bot] chore(deps): bump golang from 1.24.5 to 1.24.6 in /tests/apps/zombies-dockerfile-tini +- #7835: @dependabot[bot] chore(deps): bump golang from 1.24.5 to 1.24.6 in /tests/apps/zombies-dockerfile-no-tini +- #7834: @dependabot[bot] chore(deps): bump golang from 1.24.5 to 1.24.6 in /tests/apps/go-fail-postdeploy +- #7833: @dependabot[bot] chore(deps): bump golang from 1.24.5 to 1.24.6 in /tests/apps/gogrpc +- #7832: @dependabot[bot] chore(deps): bump golang from 1.24.5 to 1.24.6 in /tests/apps/go-fail-predeploy +- #7828: @dependabot[bot] chore(deps): bump actions/download-artifact from 4 to 5 + +### Other + +- #7854: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.18.4 to 3.18.5 in /plugins/scheduler-k3s +- #7825: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.26 to 2.11.28 in /plugins/scheduler-k3s +- #7830: @dependabot[bot] chore(deps): bump tmp from 0.2.1 to 0.2.4 in /tests/apps/multi +- #7829: @dependabot[bot] chore(deps): bump github.com/go-acme/lego/v4 from 4.24.0 to 4.25.2 in /plugins/scheduler-k3s + +## 0.36.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.36.0/bootstrap.sh +sudo DOKKU_TAG=v0.36.0 bash bootstrap.sh +``` + +See the [0.36.0 migration guide](/docs/appendices/0.36.0-migration-guide.md) for more information on migrating to 0.36.0. + +### Backwards Compatibility Breaks + +- #7442: @turicas Update how domains:clear works and add domains:reset command +- #7577: @josegonzalez Migrate DOKKU_DOCKER_STOP_TIMEOUT to ps setting + +### Bug Fixes + +- #7579: @josegonzalez Stop all app containers when calling ps:stop +- #7820: @josegonzalez Ignore brew doctor warnings when releasing Dokku as a homebrew formula +- #7811: @slava-sh Fix issue deploying from a tar file +- #7781: @josegonzalez Update gitignore to remove compiled triggers from repo + +### New Features + +- #7780: @josegonzalez Export the devcontainer environment to allow docker exec to work +- #7776: @josegonzalez Add kustomize support for applying helm charts +- #7773: @josegonzalez Use values.yaml correctly within internal Kubernetes Helm Chart +- #7769: @devopswithnaman Improve error handling in docker operations +- #7578: @josegonzalez Add ability to set maintenance mode for all cron tasks running for an app +- #7504: @josegonzalez Only redeploy formations that have had their values changed +- #7499: @nonZero Add support for custom nginx service command +- #7812: @CiTroNaK Allow specifying a custom top-level label key for the caddy proxy + +### Removals + +- #7818: @josegonzalez Drop support for Ubuntu 20.04 +- #7774: @josegonzalez Remove ansi escape characters that dropped the remote: prefix in deploy output + +### Refactors + +- #7816: @josegonzalez Use helper go functions to handle extracting files from a repository + +### Documentation + +- #7777: @coorasse Fix typo in 0.36.0 migration guide +- #7761: @largemouth Fix struct name in comment +- #7760: @olleolleolle Drop dokku-client gem, homepage 404s +- #7759: @olleolleolle Drop mention of dokkufy gem archived in 2017 +- #7755: @olleolleolle Update checkout action version in github actions documentation + +### Tests + +- #7762: @josegonzalez Reference new packeto buildpack urls + +### Dependencies + +- #7815: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.37.0 to 1.38.0 in /plugins/config +- #7814: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.73.0 to 1.74.2 in /tests/apps/gogrpc +- #7810: @dependabot[bot] chore(deps): bump github.com/spf13/pflag from 1.0.6 to 1.0.7 in /plugins/config +- #7792: @dependabot[bot] chore(deps): bump ruby from 3.4.4 to 3.4.5 in /tests/apps/dockerfile-entrypoint +- #7791: @dependabot[bot] chore(deps): bump traefik from 3.4.1 to 3.4.4 in /plugins/traefik-vhosts +- #7790: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.39.0 to 0.40.0 in /plugins/common +- #7787: @dependabot[bot] chore(deps): bump golang from 1.24.4 to 1.24.5 in /tests/apps/zombies-dockerfile-no-tini +- #7786: @dependabot[bot] chore(deps): bump golang from 1.24.4 to 1.24.5 in /tests/apps/zombies-dockerfile-tini +- #7785: @dependabot[bot] chore(deps): bump golang from 1.24.4 to 1.24.5 in /tests/apps/go-fail-predeploy +- #7784: @dependabot[bot] chore(deps): bump golang from 1.24.4 to 1.24.5 in /tests/apps/gogrpc +- #7783: @dependabot[bot] chore(deps): bump golang from 1.24.4 to 1.24.5 in /tests/apps/go-fail-postdeploy +- #7782: @josegonzalez Update golang dependencies in scheduler-k3s plugin +- #7779: @josegonzalez Update golang to 1.24 +- #7775: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.11.0 to 3.12.0 in /plugins/cron +- #7772: @dependabot[bot] chore(deps): bump timberio/vector from 0.47.0-debian to 0.48.0-debian in /plugins/logs +- #7771: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.14 to 9.6.15 in /docs/_build +- #7768: @dependabot[bot] chore(deps): bump thin from 1.8.2 to 2.0.1 in /tests/apps/ruby +- #7766: @dependabot[bot] chore(deps): bump lucaslorentz/caddy-docker-proxy from 2.9 to 2.10 in /plugins/caddy-vhosts +- #7765: @dependabot[bot] chore(deps): bump pygments from 2.19.1 to 2.19.2 in /docs/_build +- #7763: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.15 to 10.16 in /docs/_build +- #7757: @dependabot[bot] chore(deps): bump python from 3.13.4-alpine to 3.13.5-alpine in /docs/_build +- #7756: @dependabot[bot] chore(deps): bump python from 3.13.4-bookworm to 3.13.5-bookworm in /tests/apps/dockerfile-release +- #7754: @dependabot[bot] chore(deps): bump requests from 2.32.0 to 2.32.4 in /tests/apps/lambda-python +- #7753: @dependabot[bot] chore(deps): bump zipp from 3.22.0 to 3.23.0 in /docs/_build +- #7749: @josegonzalez chore: update herokuish from 0.10.3 to 0.11.0 +- #7748: @josegonzalez chore: update docker-container-healthchecker from 0.11.5 to 0.14.0 +- #7628: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/nodejs-express-noappjson +- #7621: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/nodejs-express +- #7819: @dependabot[bot] chore(deps): bump brace-expansion from 1.1.11 to 1.1.12 in /tests/apps/multi + +## 0.35.20 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.20/bootstrap.sh +sudo DOKKU_TAG=v0.35.20 bash bootstrap.sh +``` + +### Bug Fixes + +- #7726: @josegonzalez Ensure compose projects are spawned from the /tmp directory +- #7725: @josegonzalez Uninstall hashicorp tap to fix formula releases + +### Dependencies + +- #7735: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.72.2 to 1.73.0 in /tests/apps/gogrpc +- #7737: @dependabot[bot] chore(deps): bump python from 3.13.3-alpine to 3.13.4-alpine in /docs/_build +- #7738: @dependabot[bot] chore(deps): bump python from 3.13.3-bookworm to 3.13.4-bookworm in /tests/apps/dockerfile-release +- #7745: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.38.0 to 0.39.0 in /plugins/common +- #7740: @dependabot[bot] chore(deps): bump golang from 1.24.3 to 1.24.4 in /tests/apps/gogrpc +- #7741: @dependabot[bot] chore(deps): bump golang from 1.24.3 to 1.24.4 in /tests/apps/go-fail-postdeploy +- #7742: @dependabot[bot] chore(deps): bump golang from 1.24.3 to 1.24.4 in /tests/apps/zombies-dockerfile-tini +- #7743: @dependabot[bot] chore(deps): bump golang from 1.24.3 to 1.24.4 in /tests/apps/go-fail-predeploy +- #7744: @dependabot[bot] chore(deps): bump golang from 1.24.3 to 1.24.4 in /tests/apps/zombies-dockerfile-no-tini +- #7730: @dependabot[bot] chore(deps): bump traefik from 3.4.0 to 3.4.1 in /plugins/traefik-vhosts +- #7729: @dependabot[bot] chore(deps): bump zipp from 3.21.0 to 3.22.0 in /docs/_build +- #7727: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.72.1 to 1.72.2 in /tests/apps/gogrpc + +### Other + +- #7746: @dependabot[bot] chore(deps): bump django from 5.0.14 to 5.1.10 in /tests/apps/dockerfile-release +- #7731: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.24 to 2.11.25 in /plugins/scheduler-k3s + +## 0.35.19 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.19/bootstrap.sh +sudo DOKKU_TAG=v0.35.19 bash bootstrap.sh +``` + +### Bug Fixes + +- #7686: @josegonzalez Untap aws/tap to correct issues in formula release + +### Documentation + +- #7707: @swrobel Remove duplicate hostname plugin +- #7687: @d9i Fix cron key in app.json docs +- #7677: @plafue Add continuous integration example for woodpecker ci + +### Tests + +- #7585: @josegonzalez Add test to prove Dokku respects the Procfile when deploying from an image +- #7678: @josegonzalez Revert "fix: update CID count in ps tests" + +### Dependencies + +- #7692: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.37.0 to 0.38.0 in /plugins/common +- #7626: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/dockerfile +- #7693: @dependabot[bot] chore(deps): bump traefik from 3.3.5 to 3.4.0 in /plugins/traefik-vhosts +- #7694: @dependabot[bot] chore(deps): bump golang from 1.24.2 to 1.24.3 in /tests/apps/go-fail-postdeploy +- #7695: @dependabot[bot] chore(deps): bump node from 23-alpine to 24-alpine in /tests/apps/dockerfile-procfile-bad +- #7696: @dependabot[bot] chore(deps): bump golang from 1.24.2 to 1.24.3 in /tests/apps/gogrpc +- #7697: @dependabot[bot] chore(deps): bump golang from 1.24.2 to 1.24.3 in /tests/apps/go-fail-predeploy +- #7698: @dependabot[bot] chore(deps): bump node from 23-alpine to 24-alpine in /tests/apps/dockerfile-procfile +- #7699: @dependabot[bot] chore(deps): bump golang from 1.24.2 to 1.24.3 in /tests/apps/zombies-dockerfile-no-tini +- #7700: @dependabot[bot] chore(deps): bump golang from 1.24.2 to 1.24.3 in /tests/apps/zombies-dockerfile-tini +- #7701: @dependabot[bot] chore(deps): bump node from 23-alpine to 24-alpine in /tests/apps/dockerfile +- #7702: @dependabot[bot] chore(deps): bump node from 23-alpine to 24-alpine in /tests/apps/dockerfile-app-json-formations +- #7704: @dependabot[bot] chore(deps): bump rack from 2.2.13 to 2.2.14 in /tests/apps/ruby +- #7711: @dependabot[bot] chore(deps): bump flask from 3.1.0 to 3.1.1 in /tests/apps/multi +- #7712: @dependabot[bot] chore(deps): bump flask from 3.1.0 to 3.1.1 in /tests/apps/python-flask +- #7713: @dependabot[bot] chore(deps): bump pyyaml-env-tag from 0.1 to 1.1 in /docs/_build +- #7714: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.12 to 9.6.14 in /docs/_build +- #7715: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.72.0 to 1.72.1 in /tests/apps/gogrpc +- #7716: @dependabot[bot] chore(deps): bump ruby from 3.4.3 to 3.4.4 in /tests/apps/dockerfile-entrypoint +- #7718: @dependabot[bot] chore(deps): bump timberio/vector from 0.46.1-debian to 0.47.0-debian in /plugins/logs +- #7703: @dependabot[bot] chore(deps): bump node from 23-alpine to 24-alpine in /tests/apps/dockerfile-noexpose +- #7688: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.14.3 to 10.15 in /docs/_build +- #7689: @dependabot[bot] chore(deps): bump importlib-metadata from 8.6.1 to 8.7.0 in /docs/_build +- #7671: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.36.0 to 0.38.0 in /tests/apps/gogrpc +- #7681: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.71.1 to 1.72.0 in /tests/apps/gogrpc +- #7680: @dependabot[bot] chore(deps): bump soupsieve from 2.6 to 2.7 in /docs/_build +- #7682: @dependabot[bot] chore(deps): bump packaging from 24.2 to 25.0 in /docs/_build +- #7673: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.37.0 to 0.38.0 in /plugins/common +- #7675: @dependabot[bot] chore(deps): bump byjg/easy-haproxy from 4.4.0 to 4.5.0 in /plugins/haproxy-vhosts +- #7676: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.11 to 9.6.12 in /docs/_build + +### Other + +- #7723: @josegonzalez Revert "chore(deps): bump helm.sh/helm/v3 from 3.14.2 to 3.17.3 in /plugins/scheduler-k3s" +- #7645: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.14.2 to 3.17.3 in /plugins/scheduler-k3s +- #7717: @dependabot[bot] chore(deps): bump setuptools from 70.0.0 to 78.1.1 in /tests/apps/dockerfile-release +- #7719: @d1ceward Fix: Add missing `systemctl` path detection in nginx-vhost plugin +- #7679: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.15 to 2.11.24 in /plugins/scheduler-k3s + +## 0.35.18 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.18/bootstrap.sh +sudo DOKKU_TAG=v0.35.18 bash bootstrap.sh +``` + +### Bug Fixes + +- #7669: @josegonzalez fix: update CID count in ps tests + +### Dependencies + +- #7668: @dependabot[bot] chore(deps): bump timberio/vector from 0.43.1-debian to 0.46.1-debian in /plugins/logs +- #7670: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.13.3 to 4.13.4 in /docs/_build +- #7630: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/gitsubmodules +- #7666: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.36.0 to 0.37.0 in /plugins/common +- #7618: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/.websocket.disabled +- #7620: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/dockerfile-procfile-bad +- #7622: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/dockerfile-procfile +- #7629: @dependabot[bot] chore(deps): bump express from 5.0.1 to 5.1.0 in /tests/apps/checks-root +- #7623: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/dockerfile-noexpose +- #7627: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/nodejs-express-noprocfile +- #7631: @dependabot[bot] chore(deps): bump express from 4.21.2 to 5.1.0 in /tests/apps/dockerfile-app-json-formations +- #7667: @dependabot[bot] chore(deps): bump ruby from 3.4.2 to 3.4.3 in /tests/apps/dockerfile-entrypoint + +### Other + +- #7661: @josegonzalez chore: bump dokku-event-listener from 0.17.0 to 0.17.2 +- #7660: @josegonzalez chore: bump plugn from 0.15.3 to 0.16.0 +- #7659: @josegonzalez chore: bump gliderlabs-sigil from 0.11.0 to 0.11.4 +- #7656: @josegonzalez chore: bump netrc from 0.10.0 to 0.10.2 +- #7655: @josegonzalez chore: bump lambda-builder from 0.8.0 to 0.9.1 +- #7663: @josegonzalez chore: bump herokuish from 0.10.2 to 0.10.3 +- #7662: @josegonzalez chore: bump dokku-update from 0.9.4 to 0.9.6 +- #7658: @josegonzalez chore: bump sshcommand from 0.19.0 to 0.20.0 +- #7657: @josegonzalez chore: bump procfile-util from 0.19.0 to 0.20.3 +- #7654: @josegonzalez chore: bump docker-image-builder from 0.8.0 to 0.8.1 +- #7653: @josegonzalez chore: bump docker-container-healthchecker from 0.11.0 to 0.11.5 + +## 0.35.17 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.17/bootstrap.sh +sudo DOKKU_TAG=v0.35.17 bash bootstrap.sh +``` + +### Bug Fixes + +- #7606: @josegonzalez fix: bump dokku version in main.bicep when bumping azure versions +- #7650: @josegonzalez fix: set the correct default ingress class for k3s clusters +- #7649: @josegonzalez fix: decrease default in-use keda resources +- #7587: @turicas Correctly name paketo chown option +- #7584: @josegonzalez Install all builder binaries in Dokku docker image +- #7580: @josegonzalez Do not set network flag when building dockerfile images +- #7575: @josegonzalez Restore the git head ref when running repo:gc +- #7574: @josegonzalez fix: do not write VHOST file if the DOKKU_ROOT directory does not exist + +### New Features + +- #7648: @josegonzalez feat: add ability to ship k3s container logs via vector +- #7581: @josegonzalez Allow --volume flags to be passed to pack at buildtime +- #7583: @josegonzalez Send Host header when running container checks in docker-local +- #7572: @josegonzalez Add support for setting shm-size for kubernetes deployments + +### Documentation + +- #7652: @josegonzalez docs: use a cid-file in the plugin example +- #7647: @closeobserve Fix some golang docblock comments +- #7586: @turicas Remove `--` from `--git-url` in plugin installation docs + +### Dependencies + +- #7603: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.24 to 11.0.25 in /tests/apps/java +- #7641: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.12.0 to 0.13.0 in /plugins/scheduler-docker-local +- #7639: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.36.2 to 1.37.0 in /plugins/common +- #7646: @dependabot[bot] chore(deps): bump phusion/baseimage from noble-1.0.0 to noble-1.0.2 +- #7610: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.5 to 1.36.6 in /tests/apps/gogrpc +- #7619: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.71.0 to 1.71.1 in /tests/apps/gogrpc +- #7624: @dependabot[bot] chore(deps): bump traefik from 3.3.4 to 3.3.5 in /plugins/traefik-vhosts +- #7632: @dependabot[bot] chore(deps): bump golang from 1.24.1 to 1.24.2 in /tests/apps/go-fail-postdeploy +- #7633: @dependabot[bot] chore(deps): bump golang from 1.24.1 to 1.24.2 in /tests/apps/zombies-dockerfile-tini +- #7634: @dependabot[bot] chore(deps): bump golang from 1.24.1 to 1.24.2 in /tests/apps/go-fail-predeploy +- #7643: @dependabot[bot] chore(deps): bump python from 3.13.2-bookworm to 3.13.3-bookworm in /tests/apps/dockerfile-release +- #7635: @dependabot[bot] chore(deps): bump golang from 1.24.1 to 1.24.2 in /tests/apps/gogrpc +- #7636: @dependabot[bot] chore(deps): bump golang from 1.24.1 to 1.24.2 in /tests/apps/zombies-dockerfile-no-tini +- #7613: @dependabot[bot] chore(deps): bump pyparsing from 3.2.1 to 3.2.3 in /docs/_build +- #7625: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.8 to 9.6.11 in /docs/_build +- #7644: @dependabot[bot] chore(deps): bump python from 3.13.2-alpine to 3.13.3-alpine in /docs/_build +- #7651: @dependabot[bot] chore(deps): update markdown requirement from <3.8,>=3.2.1 to >=3.2.1,<3.9 in /docs/_build +- #7595: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 45.0.7 to 45.0.8 +- #7593: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.7 to 9.6.8 in /docs/_build +- #7589: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.34.0 to 0.36.0 in /tests/apps/gogrpc +- #7588: @dependabot[bot] chore(deps): bump rack from 2.2.12 to 2.2.13 in /tests/apps/ruby +- #7570: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.10.0 to 3.11.0 in /plugins/cron +- #7569: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.35.0 to 0.36.0 in /plugins/common +- #7568: @dependabot[bot] chore(deps): bump jinja2 from 3.1.5 to 3.1.6 in /docs/_build + +### Other + +- #7601: @dependabot[bot] chore(deps): bump github.com/expr-lang/expr from 1.15.8 to 1.17.0 in /plugins/scheduler-k3s +- #7602: @dependabot[bot] chore(deps): bump github.com/containerd/containerd from 1.7.20 to 1.7.27 in /plugins/scheduler-k3s +- #7637: @dependabot[bot] chore(deps): bump django from 5.0.13 to 5.0.14 in /tests/apps/dockerfile-release +- #7607: @dependabot[bot] chore(deps): bump gunicorn from 22.0.0 to 23.0.0 in /tests/apps/dockerfile-release +- #7596: @josegonzalez Drop compromisde tj-actions/changed-files from Ci +- #7582: @josegonzalez Add ability to disable vhosts for all apps +- #7576: @josegonzalez fix: properly handle NoAppsExist in network:rebuild-all and buildpacks:set +- #7571: @dependabot[bot] chore(deps): bump django from 5.0.11 to 5.0.13 in /tests/apps/dockerfile-release + +## 0.35.16 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.16/bootstrap.sh +sudo DOKKU_TAG=v0.35.16 bash bootstrap.sh +``` + +### Bug Fixes + +- #7502: @josegonzalez Ensure all report subcommands exit 0 when there is no app + +### New Features + +- #7545: @josegonzalez feat: delegate authorized_keys file validation to sshcommand + +### Documentation + +- #7547: @creature Fix display of numbered list in troubleshooting docs +- #7542: @creature Include troubleshooting details about app A being served when app B was expected due to in-app SSL redirects +- #7530: @josegonzalez Finish sentence in docker-options documentation +- #7527: @2x2xplz Add swapoff to commands when creating or updating swap space +- #7526: @ebuckthal Update vector container volume mount description + +### Dependencies + +- #7565: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.11.0 to 0.12.0 in /plugins/common +- #7566: @dependabot[bot] chore(deps): bump rack from 2.2.11 to 2.2.12 in /tests/apps/ruby +- #7567: @dependabot[bot] chore(deps): bump jinja2 from 3.1.5 to 3.1.6 in /tests/apps/python-flask +- #7563: @dependabot[bot] chore(deps): bump golang from 1.24.0 to 1.24.1 in /tests/apps/zombies-dockerfile-tini +- #7561: @dependabot[bot] chore(deps): bump golang from 1.24.0 to 1.24.1 in /tests/apps/gogrpc +- #7558: @dependabot[bot] chore(deps): bump golang from 1.24.0 to 1.24.1 in /tests/apps/zombies-dockerfile-no-tini +- #7562: @dependabot[bot] chore(deps): bump golang from 1.24.0 to 1.24.1 in /tests/apps/go-fail-predeploy +- #7555: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.5 to 9.6.7 in /docs/_build +- #7564: @dependabot[bot] chore(deps): bump golang from 1.24.0 to 1.24.1 in /tests/apps/go-fail-postdeploy +- #7560: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.11.0 to 0.12.0 in /plugins/scheduler-docker-local +- #7559: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.70.0 to 1.71.0 in /tests/apps/gogrpc +- #7553: @dependabot[bot] chore(deps): bump traefik from 3.3.3 to 3.3.4 in /plugins/traefik-vhosts +- #7551: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.33.0 to 0.35.0 in /plugins/common +- #7550: @dependabot[bot] chore(deps): bump github.com/go-jose/go-jose/v4 from 4.0.4 to 4.0.5 in /plugins/scheduler-k3s +- #7548: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.9.2 to 0.9.3 in /plugins/openresty-vhosts +- #7541: @dependabot[bot] chore(deps): bump ruby from 3.4.1 to 3.4.2 in /tests/apps/dockerfile-entrypoint +- #7537: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.29.7 to 1.29.14 in /plugins/scheduler-k3s +- #7536: @dependabot[bot] chore(deps): bump golang from 1.23.6 to 1.24.0 in /tests/apps/zombies-dockerfile-no-tini +- #7535: @dependabot[bot] chore(deps): bump golang from 1.23.6 to 1.24.0 in /tests/apps/zombies-dockerfile-tini +- #7534: @dependabot[bot] chore(deps): bump golang from 1.23.6 to 1.24.0 in /tests/apps/go-fail-predeploy +- #7533: @dependabot[bot] chore(deps): bump golang from 1.23.6 to 1.24.0 in /tests/apps/go-fail-postdeploy +- #7532: @dependabot[bot] chore(deps): bump golang from 1.23.6 to 1.24.0 in /tests/apps/gogrpc +- #7531: @dependabot[bot] chore(deps): bump rack from 2.2.8.1 to 2.2.11 in /tests/apps/ruby +- #7525: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.32.0 to 0.33.0 in /plugins/common +- #7543: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.4 to 9.6.5 in /docs/_build +- #7512: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.10.0 to 0.11.0 in /plugins/common +- #7513: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 45.0.6 to 45.0.7 +- #7514: @dependabot[bot] chore(deps): bump golang from 1.23.5 to 1.23.6 in /tests/apps/gogrpc +- #7515: @dependabot[bot] chore(deps): bump golang from 1.23.5 to 1.23.6 in /tests/apps/go-fail-predeploy +- #7516: @dependabot[bot] chore(deps): bump golang from 1.23.5 to 1.23.6 in /tests/apps/go-fail-postdeploy +- #7517: @dependabot[bot] chore(deps): bump golang from 1.23.5 to 1.23.6 in /tests/apps/zombies-dockerfile-tini +- #7518: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.13.1 to 4.13.3 in /docs/_build +- #7519: @dependabot[bot] chore(deps): bump golang from 1.23.5 to 1.23.6 in /tests/apps/zombies-dockerfile-no-tini +- #7521: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.4 to 1.36.5 in /tests/apps/gogrpc +- #7522: @dependabot[bot] chore(deps): bump python from 3.13.1-bookworm to 3.13.2-bookworm in /tests/apps/dockerfile-release +- #7523: @dependabot[bot] chore(deps): bump python from 3.13.1-alpine to 3.13.2-alpine in /docs/_build +- #7528: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.2 to 9.6.4 in /docs/_build +- #7510: @dependabot[bot] chore(deps): bump mkdocs-material from 9.6.1 to 9.6.2 in /docs/_build +- #7507: @dependabot[bot] chore(deps): bump traefik from 3.3.2 to 3.3.3 in /plugins/traefik-vhosts +- #7505: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.14.2 to 10.14.3 in /docs/_build +- #7506: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.12.3 to 4.13.1 in /docs/_build +- #7508: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.9.1 to 0.9.2 in /plugins/openresty-vhosts +- #7482: @dependabot[bot] chore(deps): bump github.com/spf13/pflag from 1.0.5 to 1.0.6 in /plugins/common +- #7500: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.30.0 to 0.33.0 in /tests/apps/gogrpc +- #7501: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.14 to 2.11.15 in /plugins/scheduler-k3s +- #7497: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.49 to 9.6.1 in /docs/_build +- #7479: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.14 to 10.14.2 in /docs/_build +- #7477: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.69.4 to 1.70.0 in /tests/apps/gogrpc +- #7476: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.2 to 1.36.4 in /tests/apps/gogrpc +- #7469: @dependabot[bot] chore(deps): bump golang from 1.23.4 to 1.23.5 in /tests/apps/go-fail-postdeploy +- #7468: @dependabot[bot] chore(deps): bump golang from 1.23.4 to 1.23.5 in /tests/apps/go-fail-predeploy +- #7467: @dependabot[bot] chore(deps): bump golang from 1.23.4 to 1.23.5 in /tests/apps/zombies-dockerfile-no-tini +- #7465: @dependabot[bot] chore(deps): bump golang from 1.23.4 to 1.23.5 in /tests/apps/zombies-dockerfile-tini +- #7466: @dependabot[bot] chore(deps): bump golang from 1.23.4 to 1.23.5 in /tests/apps/gogrpc +- #7462: @dependabot[bot] chore(deps): bump traefik from 3.3.1 to 3.3.2 in /plugins/traefik-vhosts +- #7460: @dependabot[bot] chore(deps): bump django from 5.0.10 to 5.0.11 in /tests/apps/dockerfile-release +- #7474: @dependabot[bot] chore(deps): bump importlib-metadata from 8.5.0 to 8.6.1 in /docs/_build + +### Other + +- #7554: @josegonzalez fix: update test for newer git version + +## 0.35.15 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.15/bootstrap.sh +sudo DOKKU_TAG=v0.35.15 bash bootstrap.sh +``` + +### Bug Fixes + +- #7455: @turicas Add missing post-delete scripts to checks, caddy-vhosts and haproxy-vhosts + +### Dependencies + +- #7458: @dependabot[bot] chore(deps): bump traefik from 3.3.0 to 3.3.1 in /plugins/traefik-vhosts +- #7457: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.69.2 to 1.69.4 in /tests/apps/gogrpc + +## 0.35.14 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.14/bootstrap.sh +sudo DOKKU_TAG=v0.35.14 bash bootstrap.sh +``` + +### Bug Fixes + +- #7441: @turicas Fix Vagrantfile NFS config +- #7417: @josegonzalez Write contents to stdout before writing to a file +- #7416: @josegonzalez Add missing DOKKU_LIB_HOST_ROOT to ensure plugins work in devcontainer setup +- #7412: @josegonzalez Remove cgroups dependencies + +### New Features + +- #7439: @josegonzalez Allow specifying a custom app label alias when shipping logs via vector +- #7419: @josegonzalez Add the ability to set a MAILFROM value for local cron emails + +### Refactors + +- #7415: @josegonzalez Use the golang version of CopyFromImage when copying files in bash + +### Documentation + +- #7437: @turicas Fix doc generation +- #7414: @josegonzalez Remove copyright date range + +### Dependencies + +- #7453: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.13 to 10.14 in /docs/_build +- #7451: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.31.0 to 0.32.0 in /plugins/common +- #7449: @dependabot[bot] chore(deps): bump pygments from 2.19.0 to 2.19.1 in /docs/_build +- #7450: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.1 to 1.36.2 in /tests/apps/gogrpc +- #7452: @dependabot[bot] chore(deps): bump traefik from 3.2.3 to 3.3.0 in /plugins/traefik-vhosts +- #7446: @dependabot[bot] chore(deps): bump github.com/otiai10/copy from 1.14.0 to 1.14.1 in /plugins/common +- #7445: @dependabot[bot] chore(deps): bump pygments from 2.18.0 to 2.19.0 in /docs/_build +- #7444: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 45.0.5 to 45.0.6 +- #7436: @dependabot[bot] chore(deps): bump pyparsing from 3.2.0 to 3.2.1 in /docs/_build +- #7433: @dependabot[bot] chore(deps): bump k8s.io/kubernetes from 1.29.1 to 1.29.7 in /plugins/scheduler-k3s +- #7429: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.36.1 to 1.36.2 in /plugins/common +- #7427: @dependabot[bot] chore(deps): bump click from 8.1.7 to 8.1.8 in /docs/_build +- #7425: @dependabot[bot] chore(deps): bump jinja2 from 3.1.4 to 3.1.5 in /tests/apps/python-flask +- #7426: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.36.0 to 1.36.1 in /tests/apps/gogrpc +- #7428: @dependabot[bot] chore(deps): bump jinja2 from 3.1.4 to 3.1.5 in /docs/_build +- #7430: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.12 to 10.13 in /docs/_build +- #7432: @dependabot[bot] chore(deps): bump ruby from 3.3.6 to 3.4.1 in /tests/apps/dockerfile-entrypoint +- #7424: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.69.0 to 1.69.2 in /tests/apps/gogrpc +- #7420: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.35.2 to 1.36.0 in /tests/apps/gogrpc +- #7421: @dependabot[bot] chore(deps): bump traefik from 3.2.2 to 3.2.3 in /plugins/traefik-vhosts +- #7418: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.48 to 9.5.49 in /docs/_build +- #7411: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.68.1 to 1.69.0 in /tests/apps/gogrpc + +### Other + +- #7434: @turicas Add missing checks:set to checks help +- #7422: @Tashows Fix detached tty for k3s + +## 0.35.13 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.13/bootstrap.sh +sudo DOKKU_TAG=v0.35.13 bash bootstrap.sh +``` + +### Bug Fixes + +- #7410: @josegonzalez Only raise nil responses for k8s list calls as errors +- #7403: @Tashows Properly resolve imagePullSecrets from app and deploymentID if relevant property does not exist +- #7404: @josegonzalez Correctly handle scale to zero with the Keda http addon +- #7400: @Tashows Check if tty is actually used and support proper output when it's not + +### Documentation + +- #7395: @josegonzalez Add documentation for all file formats dokku uses +- #7399: @nonZero Add -w 0 to base64 call in config:set docs for safe usage in ssh commands + +### Tests + +- #7380: @josegonzalez Run unit tests outside of docker by default + +### Dependencies + +- #7409: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.30.0 to 0.31.0 in /plugins/docker-options +- #7405: @dependabot[bot] chore(deps): bump timberio/vector from 0.42.0-debian to 0.43.1-debian in /plugins/logs +- #7407: @dependabot[bot] chore(deps): bump traefik from 3.2.1 to 3.2.2 in /plugins/traefik-vhosts +- #7402: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.36.0 to 1.36.1 in /plugins/common +- #7390: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.29.0 to 0.30.0 in /plugins/common +- #7397: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 45.0.4 to 45.0.5 +- #7398: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.47 to 9.5.48 in /docs/_build +- #7394: @dependabot[bot] chore(deps): bump django from 5.0.9 to 5.0.10 in /tests/apps/dockerfile-release +- #7385: @dependabot[bot] chore(deps): bump golang from 1.23.3 to 1.23.4 in /tests/apps/go-fail-predeploy +- #7386: @dependabot[bot] chore(deps): bump golang from 1.23.3 to 1.23.4 in /tests/apps/zombies-dockerfile-tini +- #7387: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.68.0 to 1.68.1 in /tests/apps/gogrpc +- #7388: @dependabot[bot] chore(deps): bump python from 3.13.0-alpine to 3.13.1-alpine in /docs/_build +- #7389: @dependabot[bot] chore(deps): bump six from 1.16.0 to 1.17.0 in /docs/_build +- #7392: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.9.0 to 0.10.0 in /plugins/scheduler-docker-local +- #7393: @dependabot[bot] chore(deps): bump python from 3.13.0-bookworm to 3.13.1-bookworm in /tests/apps/dockerfile-release +- #7384: @dependabot[bot] chore(deps): bump golang from 1.23.3 to 1.23.4 in /tests/apps/gogrpc +- #7383: @dependabot[bot] chore(deps): bump golang from 1.23.3 to 1.23.4 in /tests/apps/zombies-dockerfile-no-tini +- #7382: @dependabot[bot] chore(deps): bump golang from 1.23.3 to 1.23.4 in /tests/apps/go-fail-postdeploy + +## 0.35.12 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.12/bootstrap.sh +sudo DOKKU_TAG=v0.35.12 bash bootstrap.sh +``` + +### Bug Fixes + +- #7361: @josegonzalez Allow autoscaling config to have zero min replicas + +### New Features + +- #7379: @josegonzalez Provide ability to customize chart values via `scheduler-k3s:set` + +### Documentation + +- #7365: @binchengqu Correct some documentation types + +### Dependencies + +- #7378: @josegonzalez Update go modules +- #7376: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.46 to 9.5.47 in /docs/_build +- #7370: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.35.1 to 1.36.0 in /plugins/common +- #7377: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.11.9 to 2.11.14 in /plugins/scheduler-k3s +- #7369: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.35.1 to 1.36.0 in /plugins/config +- #7371: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.45 to 9.5.46 in /docs/_build +- #7366: @dependabot[bot] chore(deps): bump traefik from 3.2.0 to 3.2.1 in /plugins/traefik-vhosts +- #7364: @dependabot[bot] chore(deps): bump github.com/cert-manager/cert-manager from 1.13.3 to 1.15.4 in /plugins/scheduler-k3s +- #7363: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.44 to 9.5.45 in /docs/_build +- #7360: @dependabot[bot] chore(deps): bump flask from 3.0.3 to 3.1.0 in /tests/apps/python-flask +- #7358: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.35.1 to 1.35.2 in /tests/apps/gogrpc +- #7359: @dependabot[bot] chore(deps): bump flask from 3.0.3 to 3.1.0 in /tests/apps/multi + +## 0.35.11 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.11/bootstrap.sh +sudo DOKKU_TAG=v0.35.11 bash bootstrap.sh +``` + +### Bug Fixes + +- #7353: @indrat Add missing export DOKKU_LIB_HOST_ROOT when running dokku in container + +### New Features + +- #7355: @josegonzalez Add support for http-based scaling when deploying on the k3s scheduler + +### Documentation + +- #7349: @josegonzalez Create .well-known/funding-manifest-urls + +### Dependencies + +- #7345: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.67.1 to 1.68.0 in /tests/apps/gogrpc +- #7350: @dependabot[bot] chore(deps): bump zipp from 3.20.2 to 3.21.0 in /docs/_build +- #7351: @dependabot[bot] chore(deps): bump werkzeug from 3.1.2 to 3.1.3 in /tests/apps/python-flask +- #7347: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.28.0 to 0.29.0 in /plugins/common +- #7348: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.8.0 to 0.9.0 in /plugins/common +- #7344: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 45.0.3 to 45.0.4 +- #7343: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.8.0 to 0.9.0 in /plugins/scheduler-docker-local +- #7342: @dependabot[bot] chore(deps): bump packaging from 24.1 to 24.2 in /docs/_build +- #7341: @dependabot[bot] chore(deps): bump golang from 1.23.2 to 1.23.3 in /tests/apps/zombies-dockerfile-tini +- #7340: @dependabot[bot] chore(deps): bump golang from 1.23.2 to 1.23.3 in /tests/apps/gogrpc +- #7339: @dependabot[bot] chore(deps): bump golang from 1.23.2 to 1.23.3 in /tests/apps/go-fail-predeploy +- #7338: @dependabot[bot] chore(deps): bump golang from 1.23.2 to 1.23.3 in /tests/apps/zombies-dockerfile-no-tini +- #7337: @dependabot[bot] chore(deps): bump golang from 1.23.2 to 1.23.3 in /tests/apps/go-fail-postdeploy +- #7335: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.43 to 9.5.44 in /docs/_build +- #7334: @dependabot[bot] chore(deps): bump ruby from 3.3.5 to 3.3.6 in /tests/apps/dockerfile-entrypoint +- #7333: @dependabot[bot] chore(deps): bump werkzeug from 3.1.1 to 3.1.2 in /tests/apps/python-flask + +### Other + +- #7357: @josegonzalez Run brew cleanup before running brew doctor + +## 0.35.10 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.10/bootstrap.sh +sudo DOKKU_TAG=v0.35.10 bash bootstrap.sh +``` + +### Bug Fixes + +- #7324: @josegonzalez Correctly pull the deployment id from an app's helm values when executing dokku run under the k3s scheduler +- #7332: @josegonzalez Drop the pod name from stdout output when creating one-off containers under the k3s scheduler +- #7331: @josegonzalez Output logs for completed containers launched by the k3s scheduler +- #7330: @josegonzalez Disable stdio buffering so log coloring happens normally when fetching logs for k3s-scheduler managed apps +- #7329: @josegonzalez Correctly wait for one-off k3s pods to be ready +- #7328: @josegonzalez Execute one-off commands with DOKKU_APP_SHELL as the initial command under the k3s scheduler +- #7323: @josegonzalez Correct errors in brew doctor output when making a formula release + +### Dependencies + +- #7327: @dependabot[bot] chore(deps): bump watchdog from 5.0.3 to 6.0.0 in /docs/_build +- #7326: @dependabot[bot] chore(deps): bump werkzeug from 3.1.0 to 3.1.1 in /tests/apps/python-flask + +## 0.35.9 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.9/bootstrap.sh +sudo DOKKU_TAG=v0.35.9 bash bootstrap.sh +``` + +### Bug Fixes + +- #7321: @josegonzalez Allow nginx commands when the scheduler is set to null +- #7313: @Tashows Remove prefixing releaseName with 'dokku-' in UninstallChart and GetValues calls in scheduler-k3s/triggers.go + +### New Features + +- #7322: @josegonzalez Add debugging information to brew bump-formula-pr + +### Documentation + +- #7312: @toanalien Fix typo in nginx documentation + +### Dependencies + +- #7320: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.34.2 to 1.35.1 in /plugins/common +- #7311: @dependabot[bot] chore(deps): bump traefik from 3.1.6 to 3.2.0 in /plugins/traefik-vhosts +- #7300: @dependabot[bot] chore(deps): bump github.com/fatih/color from 1.17.0 to 1.18.0 in /plugins/common +- #7319: @dependabot[bot] chore(deps): bump werkzeug from 3.0.6 to 3.1.0 in /tests/apps/python-flask +- #7317: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.42 to 9.5.43 in /docs/_build +- #7310: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.11.2 to 10.12 in /docs/_build +- #7299: @dependabot[bot] chore(deps): bump timberio/vector from 0.41.1-debian to 0.42.0-debian in /plugins/logs +- #7306: @dependabot[bot] chore(deps): bump org.apache.maven.plugins:maven-dependency-plugin from 3.8.0 to 3.8.1 in /tests/apps/java +- #7305: @dependabot[bot] chore(deps): bump werkzeug from 3.0.4 to 3.0.6 in /tests/apps/python-flask + +## 0.35.8 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.8/bootstrap.sh +sudo DOKKU_TAG=v0.35.8 bash bootstrap.sh +``` + +### Bug Fixes + +- #7295: @josegonzalez Respect the exit code and pass flags correctly when tailing nginx logs on k3s +- #7294: @josegonzalez Tail the correct container in the ingress-nginx pod +- #7293: @josegonzalez Strip non-numeric characters from certain ingress-nginx annotation values + +### New Features + +- #7296: @josegonzalez Implement nginx:show-config for k3s plugin + +### Documentation + +- #7297: @josegonzalez Document a required ps:restart in order to apply annotations, labels, and nginx properties + +### Dependencies + +- #7287: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.9.0 to 3.10.0 in /plugins/cron +- #7288: @dependabot[bot] chore(deps): bump markupsafe from 3.0.1 to 3.0.2 in /docs/_build +- #7289: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.41 to 9.5.42 in /docs/_build + +## 0.35.7 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.7/bootstrap.sh +sudo DOKKU_TAG=v0.35.7 bash bootstrap.sh +``` + +### Bug Fixes + +- #7286: @josegonzalez Install netcat-traditional in 24.04 image +- #7284: @josegonzalez Use correct name for scheduler-proxy-logs trigger +- #7285: @josegonzalez Ensure builder pruning works when running under docker +- #7283: @danieldiekmeier Force `docker builder prune` to skip confirmation + +## 0.35.6 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.6/bootstrap.sh +sudo DOKKU_TAG=v0.35.6 bash bootstrap.sh +``` + +### Bug Fixes + +- #7279: @josegonzalez Allow exit code 2 when waiting for cloud-init to complete +- #7255: @josegonzalez Do not attempt to expose the same mapped port more than once + +### New Features + +- #7278: @josegonzalez Allow usage of newer nginx.conf.sigil on older versions of dokku +- #7271: @josegonzalez Add support for non-local nginx implementations +- #7269: @josegonzalez Add support for various timeout-related functions to nginx, openresty and k3s plugins + +### Dependencies + +- #7272: @dependabot[bot] chore(deps): bump node from 22-alpine to 23-alpine in /tests/apps/dockerfile +- #7273: @dependabot[bot] chore(deps): bump node from 22-alpine to 23-alpine in /tests/apps/dockerfile-procfile +- #7274: @dependabot[bot] chore(deps): bump node from 22-alpine to 23-alpine in /tests/apps/dockerfile-app-json-formations +- #7275: @dependabot[bot] chore(deps): bump node from 22-alpine to 23-alpine in /tests/apps/dockerfile-noexpose +- #7276: @dependabot[bot] chore(deps): bump node from 22-alpine to 23-alpine in /tests/apps/dockerfile-procfile-bad +- #7270: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.40 to 9.5.41 in /docs/_build +- #7264: @dependabot[bot] chore(deps): bump pyparsing from 3.1.4 to 3.2.0 in /docs/_build +- #7244: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.27.0 to 0.28.0 in /plugins/common +- #7260: @dependabot[bot] chore(deps): bump traefik from 3.1.5 to 3.1.6 in /plugins/traefik-vhosts +- #7259: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.39 to 9.5.40 in /docs/_build +- #7258: @dependabot[bot] chore(deps): bump markupsafe from 3.0.0 to 3.0.1 in /docs/_build +- #7254: @dependabot[bot] chore(deps): bump cookie and express in /tests/apps/checks-root +- #7246: @dependabot[bot] chore(deps): bump python from 3.12.7-alpine to 3.13.0-alpine in /docs/_build +- #7247: @dependabot[bot] chore(deps): bump python from 3.12.7-bookworm to 3.13.0-bookworm in /tests/apps/dockerfile-release +- #7248: @dependabot[bot] chore(deps): bump markupsafe from 2.1.5 to 3.0.0 in /docs/_build +- #7252: @dependabot[bot] chore(deps): bump django from 5.0.8 to 5.0.9 in /tests/apps/dockerfile-release + +### Other + +- #7282: @josegonzalez Skip ssh key setup when starting docker image +- #7281: @josegonzalez Set hostname to dokku.me for devcontainer +- #7280: @josegonzalez Merge changes from digitialocean's image-check script + +## 0.35.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.5/bootstrap.sh +sudo DOKKU_TAG=v0.35.5 bash bootstrap.sh +``` + +### New Features + +- #7213: @josegonzalez Release Dokku on supported versions of Debian and Raspbian + +### Documentation + +- #7241: @josegonzalez Clarify phase utilization in docker-options docs +- #7240: @josegonzalez Update testing docs to specify that CI is performed on Ubuntu Noble +- #7218: @josegonzalez Fix reference to arm64 +- #7215: @Lewiscowles1986 Clarify x64 as amd64/arch64 in readme + +### Tests + +- #7212: @josegonzalez Update python version used in test apps + +### Dependencies + +- #7243: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.34.2 to 1.35.1 in /tests/apps/gogrpc +- #7238: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 45.0.2 to 45.0.3 +- #7237: @dependabot[bot] chore(deps): bump traefik from 3.1.4 to 3.1.5 in /plugins/traefik-vhosts +- #7227: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.67.0 to 1.67.1 in /tests/apps/gogrpc +- #7231: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.11.1 to 10.11.2 in /docs/_build +- #7228: @dependabot[bot] chore(deps): bump golang from 1.23.1 to 1.23.2 in /tests/apps/go-fail-postdeploy +- #7229: @dependabot[bot] chore(deps): bump golang from 1.23.1 to 1.23.2 in /tests/apps/zombies-dockerfile-tini +- #7230: @dependabot[bot] chore(deps): bump python from 3.12.6-bookworm to 3.12.7-bookworm in /tests/apps/dockerfile-release +- #7232: @dependabot[bot] chore(deps): bump golang from 1.23.1 to 1.23.2 in /tests/apps/go-fail-predeploy +- #7233: @dependabot[bot] chore(deps): bump python from 3.12.6-alpine to 3.12.7-alpine in /docs/_build +- #7234: @dependabot[bot] chore(deps): bump golang from 1.23.1 to 1.23.2 in /tests/apps/zombies-dockerfile-no-tini +- #7235: @dependabot[bot] chore(deps): bump golang from 1.23.1 to 1.23.2 in /tests/apps/gogrpc +- #7220: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.38 to 9.5.39 in /docs/_build +- #7221: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.10.2 to 10.11.1 in /docs/_build +- #7222: @dependabot[bot] chore(deps): bump watchdog from 5.0.2 to 5.0.3 in /docs/_build + +### Other + +- #7245: @moenoel fix: #7035 broke traefik https router rule + +## 0.35.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.4/bootstrap.sh +sudo DOKKU_TAG=v0.35.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #7209: @josegonzalez Exit non-zero if any deployment tasks fails +- #7211: @josegonzalez Always set the git rev env var when building an app via git:sync +- #7208: @josegonzalez Compute path for docker-container-healthchecker +- #7205: @Cactusbone chore: fix vagrant dokku-windows config + +### Tests + +- #7210: @josegonzalez Ensure pack binary is installed correctly when running tests in a devcontainer + +### Dependencies + +- #7206: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.37 to 9.5.38 in /docs/_build +- #7207: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.10.1 to 10.10.2 in /docs/_build +- #7203: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.36 to 9.5.37 in /docs/_build + +## 0.35.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.3/bootstrap.sh +sudo DOKKU_TAG=v0.35.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #7200: @josegonzalez Automatically select a container type if none is specified +- #7199: @josegonzalez Check if nginx is running before reloading during an app deletion +- #7198: @josegonzalez Add builder-herokuish to default help output +- #7197: @josegonzalez Implement missing network:info command + +### Tests + +- #7201: @josegonzalez Run arm build on 22.04 + +### Dependencies + +- #7193: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.9 to 10.10.1 in /docs/_build +- #7194: @dependabot[bot] chore(deps): bump traefik from 3.1.2 to 3.1.4 in /plugins/traefik-vhosts +- #7195: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.9.0 to 0.9.1 in /plugins/openresty-vhosts +- #7196: @dependabot[bot] chore(deps): bump socket.io from 4.7.5 to 4.8.0 in /tests/apps/.websocket.disabled + +## 0.35.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.2/bootstrap.sh +sudo DOKKU_TAG=v0.35.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #7191: @josegonzalez Revert to building Dokku on Ubunut 20.04 Focal to hotfix glibc issues + +### New Features + +- #7190: @josegonzalez Allow setting most nginx properties globally + +## 0.35.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.1/bootstrap.sh +sudo DOKKU_TAG=v0.35.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #7188: @josegonzalez Respect GO environment variables when building to ensure CGO is disabled + +### Documentation + +- #7187: @josegonzalez Remove reference to old web ui + +### Dependencies + +- #7115: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.26.0 to 0.27.0 in /plugins/common +- #7160: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.10.7 to 2.11.9 in /plugins/scheduler-k3s +- #7094: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.34.1 to 1.34.2 in /plugins/common +- #7096: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.34.1 to 1.34.2 in /plugins/config +- #7109: @dependabot[bot] chore(deps): bump watchdog from 4.0.2 to 5.0.2 in /docs/_build +- #7127: @dependabot[bot] chore(deps): bump python from 3.12.5-alpine to 3.12.6-alpine in /docs/_build +- #7117: @dependabot[bot] chore(deps): bump golang from 1.23.0 to 1.23.1 in /tests/apps/go-fail-postdeploy +- #7116: @dependabot[bot] chore(deps): bump golang from 1.23.0 to 1.23.1 in /tests/apps/zombies-dockerfile-tini +- #7112: @dependabot[bot] chore(deps): bump golang from 1.23.0 to 1.23.1 in /tests/apps/zombies-dockerfile-no-tini +- #7108: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.23 to 11.0.24 in /tests/apps/java +- #7113: @dependabot[bot] chore(deps): bump golang from 1.23.0 to 1.23.1 in /tests/apps/go-fail-predeploy +- #7110: @dependabot[bot] chore(deps): bump ruby from 3.3.4 to 3.3.5 in /tests/apps/dockerfile-entrypoint +- #7114: @dependabot[bot] chore(deps): bump golang from 1.23.0 to 1.23.1 in /tests/apps/gogrpc +- #7132: @dependabot[bot] chore(deps): bump express from 4.19.2 to 4.21.0 in /tests/apps/dockerfile-procfile-bad +- #7133: @dependabot[bot] chore(deps): bump express from 4.19.2 to 4.21.0 in /tests/apps/nodejs-express-noappjson +- #7134: @dependabot[bot] chore(deps): bump express from 4.19.2 to 4.21.0 in /tests/apps/gitsubmodules +- #7135: @dependabot[bot] chore(deps): bump express from 4.19.2 to 4.21.0 in /tests/apps/nodejs-express-noprocfile +- #7136: @dependabot[bot] chore(deps): bump express from 4.19.2 to 4.21.0 in /tests/apps/dockerfile-procfile +- #7137: @dependabot[bot] chore(deps): bump express from 4.19.2 to 4.21.0 in /tests/apps/dockerfile-app-json-formations +- #7140: @dependabot[bot] chore(deps): bump express from 4.19.2 to 4.21.0 in /tests/apps/.websocket.disabled +- #7141: @dependabot[bot] chore(deps): bump express from 4.19.2 to 4.21.0 in /tests/apps/nodejs-express +- #7161: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.65.0 to 1.67.0 in /tests/apps/gogrpc +- #7153: @dependabot[bot] chore(deps): bump luizm/action-sh-checker from 0.8.0 to 0.9.0 +- #7152: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 45.0.0 to 45.0.2 +- #7079: @dependabot[bot] chore(deps): bump pyparsing from 3.1.2 to 3.1.4 in /docs/_build +- #7097: @dependabot[bot] chore(deps): bump mkdocs from 1.6.0 to 1.6.1 in /docs/_build +- #7142: @dependabot[bot] chore(deps): bump importlib-metadata from 8.4.0 to 8.5.0 in /docs/_build +- #7186: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.33 to 9.5.36 in /docs/_build +- #7144: @dependabot[bot] chore(deps): bump zipp from 3.20.0 to 3.20.2 in /docs/_build + +## 0.35.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.35.0/bootstrap.sh +sudo DOKKU_TAG=v0.35.0 bash bootstrap.sh +``` + +See the [0.35.0 migration guide](/docs/appendices/0.35.0-migration-guide.md) for more information on migrating to 0.35.0. + +### Bug Fixes + +- #7179: @josegonzalez Remove references to version in compose files +- #7177: @josegonzalez Reference noble apt repository and not focal +- #7164: @josegonzalez Remove systemd on docker-based installations +- #7157: @Cactusbone Fix building in vagrant when jq is not available +- #7147: @Cactusbone Support older docker versions by setting ARG value with an equal sign +- #7088: @andreby Handle the case where parent PID is 0 +- #7077: @bakatz Add cron dependency because it's necessary to run scheduled cron jobs + +### New Features + +- #7185: @josegonzalez Fix vagrant running on arm64 machines +- #7184: @josegonzalez Clear out docker builder cache once a day +- #7183: @josegonzalez Error out when the Dockerfile is missing during builder-dockerfile execution +- #7175: @josegonzalez Upgrade digitalocean image to Ubuntu Noble +- #7174: @josegonzalez Upgrade vagrant image to Ubuntu Noble +- #7173: @josegonzalez Upgrade builder docker image to Ubuntu Noble +- #7166: @josegonzalez Upgrade herokuish from 0.9.2 to 0.10.1 +- #7075: @josegonzalez Upgrade herokuish builder to gliderlabs/herokuish:latest-24 +- #7074: @josegonzalez Upgrade cnb builder to heroku/builder:24 +- #7071: @josegonzalez Bump go version to 1.23.0 +- #6853: @Reggino Support Ubuntu 24.04 LTS +- #6762: @josegonzalez Upgrade docker compose version in use + +### Documentation + +- #7180: @josegonzalez Reference actual properties for enabling the traefik api and dashboard +- #7178: @josegonzalez Fix link to docs site +- #7101: @strugee Fix typo in CNB documentation +- #7100: @no0dles SVG gets modified on release + +### Tests + +- #7176: @josegonzalez Fetch packages installed in ci from Ubuntu Noble repo +- #7172: @josegonzalez Run CI release processes on Ubuntu Noble +- #7171: @josegonzalez Run CI on Ubuntu Noble +- #7170: @josegonzalez Run CI codeql analysis on Ubuntu Noble +- #7169: @josegonzalez Run CI doc generation on Ubuntu Noble +- #7168: @josegonzalez Run CI linting on Ubuntu Noble +- #7073: @josegonzalez Drop codacy + +### Dependencies + +- #7148: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.8.0 to 0.9.0 in /plugins/openresty-vhosts +- #7139: @dependabot[bot] chore(deps): bump timberio/vector from 0.39.0-debian to 0.41.1-debian in /plugins/logs +- #7060: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.8.0 to 3.9.0 in /plugins/cron +- #7035: @dependabot[bot] chore(deps): bump traefik from 2.11.2 to 3.1.2 in /plugins/traefik-vhosts +- #7014: @dependabot[bot] chore(deps): bump timberio/vector from 0.39.0-debian to 0.40.0-debian in /plugins/logs +- #6985: @dependabot[bot] chore(deps): bump github.com/traefik/traefik/v2 from 2.10.7 to 2.11.6 in /plugins/scheduler-k3s + +### Other + +- #7165: @josegonzalez Upgrade Docker image to Ubuntu Noble 24.04 +- #6784: @josegonzalez Remove --restart docker arguments when not running deploy-phase containers + +## 0.34.9 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.9/bootstrap.sh +sudo DOKKU_TAG=v0.34.9 bash bootstrap.sh +``` + +### Documentation + +- #7062: @Tashows List dokku-image-size-limit plugin + +### Dependencies + +- #7070: @dependabot[bot] chore(deps): bump micromatch from 4.0.5 to 4.0.8 in /tests/apps/multi +- #7064: @dependabot[bot] chore(deps): bump importlib-metadata from 8.3.0 to 8.4.0 in /docs/_build +- #7068: @dependabot[bot] chore(deps): bump werkzeug from 3.0.3 to 3.0.4 in /tests/apps/python-flask +- #7065: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.22 to 11.0.23 in /tests/apps/java +- #7066: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.5.7 to 45.0.0 +- #7067: @dependabot[bot] chore(deps): bump org.apache.maven.plugins:maven-dependency-plugin from 3.7.1 to 3.8.0 in /tests/apps/java +- #7069: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.32 to 9.5.33 in /docs/_build +- #7063: @dependabot[bot] chore(deps): bump importlib-metadata from 8.2.0 to 8.3.0 in /docs/_build +- #7058: @dependabot[bot] chore(deps): update markdown requirement from <3.7,>=3.2.1 to >=3.2.1,<3.8 in /docs/_build +- #7059: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.31 to 9.5.32 in /docs/_build + +## 0.34.8 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.8/bootstrap.sh +sudo DOKKU_TAG=v0.34.8 bash bootstrap.sh +``` + +### Documentation + +- #7046: @bakatz Fixes incorrect documentation link for null builder +- #6983: @polettix Clarify paths used by Dokku when looking for specific files +- #7012: @DavidTheProgrammer Fix grammar under HSTS Header section in ssl documentation +- #7019: @josegonzalez Clarify to users that the migration guides should be followed for upgrades + +### Tests + +- #7039: @josegonzalez Correct case of pip in builder-pack test output + +### Dependencies + +- #7057: @josegonzalez Update golang dependencies +- #7048: @dependabot[bot] chore(deps): bump soupsieve from 2.5 to 2.6 in /docs/_build +- #7041: @dependabot[bot] chore(deps): bump zipp from 3.19.2 to 3.20.0 in /docs/_build +- #7044: @dependabot[bot] chore(deps): bump gunicorn from 22.0.0 to 23.0.0 in /tests/apps/multi +- #7042: @dependabot[bot] chore(deps): bump watchdog from 4.0.1 to 4.0.2 in /docs/_build +- #7043: @dependabot[bot] chore(deps): bump gunicorn from 22.0.0 to 23.0.0 in /tests/apps/python-flask +- #7045: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.25.0 to 0.26.0 in /plugins/common +- #7050: @dependabot[bot] chore(deps): bump golang from 1.22.6 to 1.23.0 in /tests/apps/zombies-dockerfile-tini +- #7049: @dependabot[bot] chore(deps): bump golang from 1.22.6 to 1.23.0 in /tests/apps/zombies-dockerfile-no-tini +- #7051: @dependabot[bot] chore(deps): bump golang from 1.22.6 to 1.23.0 in /tests/apps/go-fail-predeploy +- #7052: @dependabot[bot] chore(deps): bump golang from 1.22.6 to 1.23.0 in /tests/apps/gogrpc +- #7053: @dependabot[bot] chore(deps): bump golang from 1.22.6 to 1.23.0 in /tests/apps/go-fail-postdeploy +- #7022: @dependabot[bot] chore(deps): bump github.com/docker/docker from 24.0.9+incompatible to 25.0.6+incompatible in /plugins/scheduler-k3s +- #7016: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.34.0 to 1.34.1 in /plugins/config +- #7036: @dependabot[bot] chore(deps): bump django from 5.0.7 to 5.0.8 in /tests/apps/dockerfile-release +- #7025: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.7.0 to 0.8.0 in /plugins/common +- #7034: @dependabot[bot] chore(deps): bump golang from 1.22.5 to 1.22.6 in /tests/apps/gogrpc +- #7033: @dependabot[bot] chore(deps): bump golang from 1.22.5 to 1.22.6 in /tests/apps/go-fail-postdeploy +- #7030: @dependabot[bot] chore(deps): bump golang from 1.22.5 to 1.22.6 in /tests/apps/go-fail-predeploy +- #7015: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.34.0 to 1.34.1 in /plugins/common +- #7011: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.8.1 to 10.9 in /docs/_build +- #7037: @dependabot[bot] chore(deps): bump python from 3.12.4-alpine to 3.12.5-alpine in /docs/_build +- #7023: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.30 to 9.5.31 in /docs/_build +- #7032: @dependabot[bot] chore(deps): bump pyyaml from 6.0.1 to 6.0.2 in /docs/_build +- #7031: @dependabot[bot] chore(deps): bump golang from 1.22.5 to 1.22.6 in /tests/apps/zombies-dockerfile-tini +- #7021: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.5.6 to 44.5.7 +- #7029: @dependabot[bot] chore(deps): bump golang from 1.22.5 to 1.22.6 in /tests/apps/zombies-dockerfile-no-tini +- #6999: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.5.5 to 44.5.6 +- #7002: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.12.0 to 0.12.1 in /plugins/ps +- #7004: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.28 to 9.5.30 in /docs/_build +- #7006: @dependabot[bot] chore(deps): bump importlib-metadata from 8.0.0 to 8.2.0 in /docs/_build +- #7007: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.33.1 to 1.34.0 in /plugins/common +- #7008: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.33.1 to 1.34.0 in /plugins/config +- #6996: @dependabot[bot] chore(deps): bump setuptools from 68.0.0 to 70.0.0 in /tests/apps/dockerfile-release + +## 0.34.7 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.7/bootstrap.sh +sudo DOKKU_TAG=v0.34.7 bash bootstrap.sh +``` + +### Bug Fixes + +- #6984: @josegonzalez Correctly pass around arguments when displaying container log output + +### New Features + +- #6992: @josegonzalez feat: add logging when hsts nginx config is not using built-in template + +### Dependencies + +- #6988: @dependabot[bot] chore(deps): bump django from 5.0.6 to 5.0.7 in /tests/apps/dockerfile-release +- #6987: @dependabot[bot] chore(deps): bump ruby from 3.3.3 to 3.3.4 in /tests/apps/dockerfile-entrypoint +- #6972: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.9.0 to 0.12.0 in /plugins/ps +- #6898: @josegonzalez Update all dependencies in the dokku ecosystem +- #6978: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.21 to 11.0.22 in /tests/apps/java +- #6961: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.9.0 to 0.11.0 in /plugins/ps +- #6981: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.24.0 to 0.25.0 in /plugins/common +- #6980: @dependabot[bot] chore(deps): bump golang from 1.22.4 to 1.22.5 in /tests/apps/gogrpc +- #6979: @dependabot[bot] chore(deps): bump golang from 1.22.4 to 1.22.5 in /tests/apps/go-fail-postdeploy +- #6977: @dependabot[bot] chore(deps): bump golang from 1.22.4 to 1.22.5 in /tests/apps/zombies-dockerfile-tini +- #6976: @dependabot[bot] chore(deps): bump golang from 1.22.4 to 1.22.5 in /tests/apps/zombies-dockerfile-no-tini +- #6975: @dependabot[bot] chore(deps): bump golang from 1.22.4 to 1.22.5 in /tests/apps/go-fail-predeploy +- #6971: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.64.0 to 1.65.0 in /tests/apps/gogrpc +- #6963: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.27 to 9.5.28 in /docs/_build +- #6956: @dependabot[bot] chore(deps): bump importlib-metadata from 7.2.0 to 8.0.0 in /docs/_build +- #6951: @dependabot[bot] chore(deps): bump org.apache.maven.plugins:maven-dependency-plugin from 3.7.0 to 3.7.1 in /tests/apps/java +- #6952: @dependabot[bot] chore(deps): bump importlib-metadata from 7.2.0 to 7.2.1 in /docs/_build +- #6955: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.5.3 to 44.5.5 +- #6957: @dependabot[bot] chore(deps): bump djangorestframework from 3.14.0 to 3.15.2 in /tests/apps/dockerfile-release +- #6958: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.8.1 to 0.9.0 in /plugins/ps + +## 0.34.6 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.6/bootstrap.sh +sudo DOKKU_TAG=v0.34.6 bash bootstrap.sh +``` + +### Bug Fixes + +- #6949: @josegonzalez Set permissions correctly on all files in the builder-release process + +### Documentation + +- #6948: @anand2312 Fix grammar in docs/getting-started/advanced-installation + +### Dependencies + +- #6943: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.5.2 to 44.5.3 +- #6940: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.26 to 9.5.27 in /docs/_build +- #6941: @dependabot[bot] chore(deps): bump timberio/vector from 0.38.0-debian to 0.39.0-debian in /plugins/logs +- #6946: @dependabot[bot] chore(deps): bump importlib-metadata from 7.1.0 to 7.2.0 in /docs/_build + +## 0.34.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.5/bootstrap.sh +sudo DOKKU_TAG=v0.34.5 bash bootstrap.sh +``` + +### Bug Fixes + +- #6835: @erickedji Add missing local RE_IPV4 & RE_IPV6 vars in core-post-deploy + +### New Features + +- #6808: @Coffee2CodeNL Traefik optional custom http https entrypoints + +### Documentation + +- #6937: @irth docs: add `network_mode: bridge` to the docker compose example +- #6871: @chrisjsimpson Update dockerfile example in port management docs +- #6857: @josegonzalez Update link to dokku development blog +- #6821: @andrew-womeldorf Add tailscale plugin to community plugin list +- #6823: @powdahound Remove errant tab character in cron:set help text +- #6809: @alexislefebvre Update CI badge in readme + +### Dependencies + +- #6934: @dependabot[bot] chore(deps): bump org.apache.maven.plugins:maven-dependency-plugin from 3.6.1 to 3.7.0 in /tests/apps/java +- #6935: @dependabot[bot] chore(deps): bump braces from 3.0.2 to 3.0.3 in /tests/apps/multi +- #6938: @dependabot[bot] chore(deps): bump ruby from 3.3.2 to 3.3.3 in /tests/apps/dockerfile-entrypoint +- #6922: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.23.0 to 0.24.0 in /plugins/common +- #6929: @dependabot[bot] chore(deps): bump python from 3.12.3-alpine to 3.12.4-alpine in /docs/_build +- #6925: @dependabot[bot] chore(deps): bump zipp from 3.18.2 to 3.19.2 in /docs/_build +- #6914: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.5.1 to 44.5.2 +- #6916: @dependabot[bot] chore(deps): bump ruby from 3.3.1 to 3.3.2 in /tests/apps/dockerfile-entrypoint +- #6921: @dependabot[bot] chore(deps): bump golang from 1.22.3 to 1.22.4 in /tests/apps/zombies-dockerfile-no-tini +- #6920: @dependabot[bot] chore(deps): bump golang from 1.22.3 to 1.22.4 in /tests/apps/zombies-dockerfile-tini +- #6923: @dependabot[bot] chore(deps): bump golang from 1.22.3 to 1.22.4 in /tests/apps/go-fail-postdeploy +- #6915: @dependabot[bot] chore(deps): bump lucaslorentz/caddy-docker-proxy from 2.8 to 2.9 in /plugins/caddy-vhosts +- #6924: @dependabot[bot] chore(deps): bump golang from 1.22.3 to 1.22.4 in /tests/apps/gogrpc +- #6927: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.24 to 9.5.26 in /docs/_build +- #6926: @dependabot[bot] chore(deps): bump golang from 1.22.3 to 1.22.4 in /tests/apps/go-fail-predeploy +- #6930: @dependabot[bot] chore(deps): bump packaging from 24.0 to 24.1 in /docs/_build +- #6931: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.34.1 to 1.34.2 in /tests/apps/gogrpc +- #6903: @dependabot[bot] chore(deps): bump requests from 2.31.0 to 2.32.0 in /tests/apps/lambda-python +- #6901: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.23 to 9.5.24 in /docs/_build +- #6908: @dependabot[bot] chore(deps): bump watchdog from 4.0.0 to 4.0.1 in /docs/_build +- #6909: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.4.0 to 44.5.1 +- #6873: @dependabot[bot] chore(deps): bump werkzeug from 3.0.2 to 3.0.3 in /tests/apps/python-flask +- #6892: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.3.0 to 44.4.0 +- #6891: @dependabot[bot] chore(deps): bump github.com/fatih/color from 1.16.0 to 1.17.0 in /plugins/common +- #6886: @dependabot[bot] chore(deps): bump golang from 1.22.2 to 1.22.3 in /tests/apps/gogrpc +- #6894: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.63.2 to 1.64.0 in /tests/apps/gogrpc +- #6888: @dependabot[bot] chore(deps): bump timberio/vector from 0.36.1-debian to 0.38.0-debian in /plugins/logs +- #6885: @dependabot[bot] chore(deps): bump golang from 1.22.2 to 1.22.3 in /tests/apps/zombies-dockerfile-tini +- #6887: @dependabot[bot] chore(deps): bump golang from 1.22.2 to 1.22.3 in /tests/apps/go-fail-postdeploy +- #6883: @dependabot[bot] chore(deps): bump golang from 1.22.2 to 1.22.3 in /tests/apps/zombies-dockerfile-no-tini +- #6879: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.34.0 to 1.34.1 in /tests/apps/gogrpc +- #6874: @dependabot[bot] chore(deps): bump jinja2 from 3.1.3 to 3.1.4 in /tests/apps/python-flask +- #6876: @dependabot[bot] chore(deps): bump pygments from 2.17.2 to 2.18.0 in /docs/_build +- #6875: @dependabot[bot] chore(deps): bump dokku/openresty-docker-proxy from 0.7.0 to 0.8.0 in /plugins/openresty-vhosts +- #6878: @dependabot[bot] chore(deps): bump jinja2 from 3.1.3 to 3.1.4 in /docs/_build +- #6880: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.22.0 to 0.23.0 in /plugins/common +- #6884: @dependabot[bot] chore(deps): bump golang from 1.22.2 to 1.22.3 in /tests/apps/go-fail-predeploy +- #6897: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.20 to 11.0.21 in /tests/apps/java +- #6896: @dependabot[bot] chore(deps): bump zipp from 3.18.1 to 3.18.2 in /docs/_build +- #6895: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.20 to 9.5.23 in /docs/_build +- #6858: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.8 to 10.8.1 in /docs/_build +- #6859: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.19 to 9.5.20 in /docs/_build +- #6860: @dependabot[bot] chore(deps): bump node from 21-alpine to 22-alpine in /tests/apps/dockerfile-procfile +- #6861: @dependabot[bot] chore(deps): bump node from 21-alpine to 22-alpine in /tests/apps/dockerfile-procfile-bad +- #6862: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.33.0 to 1.33.1 in /plugins/common +- #6863: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.33.0 to 1.33.1 in /plugins/config +- #6865: @dependabot[bot] chore(deps): bump node from 21-alpine to 22-alpine in /tests/apps/dockerfile +- #6866: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.33.0 to 1.34.0 in /tests/apps/gogrpc +- #6867: @dependabot[bot] chore(deps): bump node from 21-alpine to 22-alpine in /tests/apps/dockerfile-noexpose +- #6868: @dependabot[bot] chore(deps): bump node from 21-alpine to 22-alpine in /tests/apps/dockerfile-app-json-formations +- #6849: @dependabot[bot] chore(deps): bump mkdocs from 1.5.3 to 1.6.0 in /docs/_build +- #6852: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.18 to 9.5.19 in /docs/_build +- #6850: @dependabot[bot] chore(deps): bump ruby from 3.3.0 to 3.3.1 in /tests/apps/dockerfile-entrypoint +- #6845: @josegonzalez chore: update dependencies +- #6843: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.32.0 to 1.33.0 in /plugins/common +- #6844: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.32.0 to 1.33.0 in /plugins/config +- #6842: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.1.0 to 44.3.0 +- #6841: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.7.1 to 10.8 in /docs/_build +- #6839: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.21.0 to 0.23.0 in /plugins/config +- #6837: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.21.0 to 0.23.0 in /tests/apps/gogrpc +- #6838: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.21.0 to 0.23.0 in /plugins/common +- #6840: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.21.0 to 0.23.0 in /plugins/scheduler-k3s +- #6836: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.0.1 to 44.1.0 +- #6831: @dependabot[bot] chore(deps): bump gunicorn from 21.2.0 to 22.0.0 in /tests/apps/python-flask +- #6829: @dependabot[bot] chore(deps): bump gunicorn from 20.1.0 to 22.0.0 in /tests/apps/dockerfile-release +- #6830: @dependabot[bot] chore(deps): bump gunicorn from 21.2.0 to 22.0.0 in /tests/apps/multi +- #6828: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.17 to 9.5.18 in /docs/_build +- #6825: @dependabot[bot] chore(deps): bump sqlparse from 0.4.4 to 0.5.0 in /tests/apps/dockerfile-release +- #6819: @dependabot[bot] chore(deps): bump traefik from 2.11.1 to 2.11.2 in /plugins/traefik-vhosts +- #6817: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 44.0.0 to 44.0.1 +- #6816: @dependabot[bot] chore(deps): bump python from 3.12.2-alpine to 3.12.3-alpine in /docs/_build +- #6818: @dependabot[bot] chore(deps): bump traefik from 2.11.0 to 2.11.1 in /plugins/traefik-vhosts +- #6810: @dependabot[bot] chore(deps): bump flask from 3.0.2 to 3.0.3 in /tests/apps/python-flask +- #6811: @dependabot[bot] chore(deps): bump flask from 3.0.2 to 3.0.3 in /tests/apps/multi +- #6812: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.63.0 to 1.63.2 in /tests/apps/gogrpc + +### Other + +- #6855: @kcdragon Update Health Check documentation to include `port` + +## 0.34.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.4/bootstrap.sh +sudo DOKKU_TAG=v0.34.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #6801: @josegonzalez Guard against invalid proxy values due to move of port mapping to ports plugin +- #6798: @josegonzalez Implement missing scheduler-app-status trigger in k3s scheduler plugin +- #6791: @josegonzalez Correct issue where pre-deploy scripts do not have root access + +### New Features + +- #6800: @josegonzalez Update message for deployment tasks that execute in ephemeral containers + +### Documentation + +- #6799: @josegonzalez Document that a registry is required for k3s usage + +### Tests + +- #6802: @josegonzalez Update test for new deployment task output + +### Dependencies + +- #6807: @josegonzalez chore: bump go modules +- #6805: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.6.0 to 0.7.0 in /plugins/common +- #6806: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.21.0 to 0.22.0 in /plugins/common +- #6803: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.6.0 to 0.7.0 in /plugins/scheduler-docker-local +- #6804: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.6.0 to 0.7.0 in /plugins/app-json +- #6794: @dependabot[bot] chore(deps): bump golang from 1.22.1 to 1.22.2 in /tests/apps/zombies-dockerfile-tini +- #6796: @dependabot[bot] chore(deps): bump golang from 1.22.1 to 1.22.2 in /tests/apps/gogrpc +- #6793: @dependabot[bot] chore(deps): bump golang from 1.22.1 to 1.22.2 in /tests/apps/zombies-dockerfile-no-tini +- #6797: @dependabot[bot] chore(deps): bump golang from 1.22.1 to 1.22.2 in /tests/apps/go-fail-postdeploy +- #6795: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.62.1 to 1.63.0 in /tests/apps/gogrpc +- #6792: @dependabot[bot] chore(deps): bump golang from 1.22.1 to 1.22.2 in /tests/apps/go-fail-predeploy + +## 0.34.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.3/bootstrap.sh +sudo DOKKU_TAG=v0.34.3 bash bootstrap.sh +``` + +### New Features + +- #6785: @josegonzalez Update herokuish requirement to better support readonly containers + +### Documentation + +- #6788: @bumblefudge Fix typos in nginx documentation +- #6789: @crazehang Fix typos in documentation + +### Dependencies + +- #6786: @dependabot[bot] chore(deps): bump werkzeug from 3.0.1 to 3.0.2 in /tests/apps/python-flask +- #6787: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.16 to 9.5.17 in /docs/_build +- #6782: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.15 to 9.5.16 in /docs/_build + +## 0.34.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.2/bootstrap.sh +sudo DOKKU_TAG=v0.34.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #6779: @josegonzalez Add --global flag to ps:set +- #6778: @josegonzalez Popd out of tmp directory after a git push-based deployment +- #6777: @josegonzalez Use correct function name in openresty:report output + +### Documentation + +- #6781: @josegonzalez Update builder management docs to warn that globally setting a builder will force all apps to use that builder +- #6780: @josegonzalez Clarify ssh-keys commands to run when adding ssh keys in docker-based installation + +### Other + +- #6776: @josegonzalez Use a long git sha in the git:report output + +## 0.34.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.1/bootstrap.sh +sudo DOKKU_TAG=v0.34.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #6775: @josegonzalez Ensure files get created with the correct permissions when building with herokuish +- #6739: @AndrewKvalheim Dereference annotated git tags when deploying via git:sync +- #6743: @testwill Fix typo in app-json output + +### New Features + +- #6768: @josegonzalez Migrate away from transitional packages + +### Documentation + +- #6763: @iloveitaly Add docker-compose example to installation docs +- #6757: @josegonzalez Add documentation issue template +- #6742: @josegonzalez Remove trailing whitespace from history doc + +### Dependencies + +- #6772: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 43.0.1 to 44.0.0 +- #6766: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.14 to 9.5.15 in /docs/_build +- #6744: @dependabot[bot] chore(deps): bump github.com/docker/docker from 24.0.7+incompatible to 24.0.9+incompatible in /plugins/scheduler-k3s +- #6754: @dependabot[bot] chore(deps): bump express from 4.18.3 to 4.19.1 in /tests/apps/dockerfile-app-json-formations +- #6745: @dependabot[bot] chore(deps): bump express from 4.18.3 to 4.19.1 in /tests/apps/nodejs-express +- #6746: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 43.0.0 to 43.0.1 +- #6747: @dependabot[bot] chore(deps): bump express from 4.18.3 to 4.19.1 in /tests/apps/.websocket.disabled +- #6748: @dependabot[bot] chore(deps): bump express from 4.18.3 to 4.19.1 in /tests/apps/nodejs-express-noprocfile +- #6749: @dependabot[bot] chore(deps): bump express from 4.18.3 to 4.19.1 in /tests/apps/nodejs-express-noappjson +- #6750: @dependabot[bot] chore(deps): bump express from 4.18.3 to 4.19.1 in /tests/apps/dockerfile-procfile-bad +- #6751: @dependabot[bot] chore(deps): bump importlib-metadata from 7.0.2 to 7.1.0 in /docs/_build +- #6752: @dependabot[bot] chore(deps): bump express from 4.18.3 to 4.19.1 in /tests/apps/dockerfile-procfile +- #6753: @dependabot[bot] chore(deps): bump express from 4.18.3 to 4.19.1 in /tests/apps/gitsubmodules +- #6741: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.31.1 to 1.32.0 in /plugins/common +- #6740: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.31.1 to 1.32.0 in /plugins/config + +## 0.34.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.34.0/bootstrap.sh +sudo DOKKU_TAG=v0.34.0 bash bootstrap.sh +``` + +See the [0.34.0 migration guide](/docs/appendices/0.34.0-migration-guide.md) for more information on migrating to 0.34.0. + +### Bug Fixes + +- #6724: @josegonzalez Correctly check if a repository has code or not in git plugin +- #6721: @josegonzalez Correct issue where cron properties cannot be retrieved if global +- #6720: @josegonzalez Add nginx-property binary to gitignore +- #6708: @josegonzalez Correctly handle quoted spaces over ssh +- #6707: @josegonzalez Correct issue where removing by index failed due to incorrect input validation +- #6705: @josegonzalez Ensure we cleanup the data directory on app deletion +- #6703: @josegonzalez Use copytruncate in logrotate commands + +### New Features + +- #6725: @josegonzalez Warn when publish ports if scaling up or zero downtime is enabled +- #6719: @josegonzalez Add a make target to copy the dokku binary over +- #6717: @josegonzalez Add ability to only build synced repo if there are changes +- #6715: @josegonzalez Add disk utilization to report output +- #6704: @josegonzalez Switch the default k3s routing layer from traefik to nginx + +### Removals + +- #6700: @josegonzalez Remove priority property from traefik plugin + +### Refactors + +- #6714: @josegonzalez Use CallPlugnTrigger instead of PlugnTriggerSetup +- #6713: @josegonzalez Use CallPlugnTrigger instead of PlugnTrigger +- #6712: @josegonzalez Use CallPlugnTrigger instead of PlugnTriggerOutput +- #6711: @josegonzalez Use CallPlugnTrigger instead of PlugnTriggerOutputAsString +- #6681: @josegonzalez Migrate to single ingress per app/domain combination when using nginx for k3s proxying +- #6156: @josegonzalez Migrate the app deploy lock to the data directory + +### Documentation + +- #6738: @josegonzalez Update migration guide for 0.34.x +- #6731: @strugee Clarify alternate proxy support status + +### Dependencies + +- #6736: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.13 to 9.5.14 in /docs/_build +- #6729: @dependabot[bot] chore(deps): update markdown requirement from <3.6,>=3.2.1 to >=3.2.1,<3.7 in /docs/_build +- #6728: @dependabot[bot] chore(deps): bump zipp from 3.17.0 to 3.18.1 in /docs/_build +- #6727: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.1.0 to 43.0.0 +- #6723: @dependabot[bot] chore(deps): bump zipp from 3.17.0 to 3.18.0 in /docs/_build +- #6722: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.1.0 to 43.0.0 +- #6710: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.32.0 to 1.33.0 in /plugins/scheduler-k3s +- #6709: @dependabot[bot] chore(deps): bump zipp from 3.17.0 to 3.18.0 in /docs/_build + +### Other + +- #6718: @josegonzalez Write current process id to all acquired lock files +- #6706: @josegonzalez Remove git:unlock command +- #6650: @bastianh Do not expose all containers via traefik by default +- #6632: @josegonzalez Simplify file ownership in the container by setting the process ownership during the build process +- #6630: @josegonzalez Remove unused code for copying cache directories + +## 0.33.9 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.9/bootstrap.sh +sudo DOKKU_TAG=v0.33.9 bash bootstrap.sh +``` + +### Bug Fixes + +- #6695: @josegonzalez Do not attempt to install both k3s and traefik +- #6687: @josegonzalez Correct issue where ClusterTriggerAuthentication objects were filtered based on prefix +- #6686: @josegonzalez Validate input for the autoscaling-auth:set command +- #6678: @josegonzalez Handle case where most builders didn't detect amd64 images on arm64 +- #6677: @josegonzalez Handle case where keda isn't installed properly +- #6676: @josegonzalez Drop extra logging call from app-json plugin +- #6674: @josegonzalez Remove systemd reverse dependency by dropping software-properties-common + +### New Features + +- #6698: @josegonzalez Upgrade ingress-nginx +- #6588: @taraszka Add extra tags to every image dokku push when push-extra-tags are set +- #6680: @josegonzalez Wait until helm charts are installed before marking a deploy as failed or completed + +### Documentation + +- #6699: @josegonzalez Add a placeholder migration guide for 0.33.0 +- #6682: @josegonzalez Fix reference to formation key in autoscaling example + +### Tests + +- #6696: @josegonzalez Run k3s tests with a specified server ip +- #6679: @josegonzalez Split out k3s tests to speed up ci + +### Dependencies + +- #6697: @dependabot[bot] chore(deps): bump timberio/vector from 0.36.0-debian to 0.36.1-debian in /plugins/logs +- #6685: @dependabot[bot] chore(deps): bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 in /plugins/scheduler-k3s +- #6692: @dependabot[bot] chore(deps): bump packaging from 23.2 to 24.0 in /docs/_build +- #6693: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.0.7 to 42.1.0 +- #6689: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.0.6 to 42.0.7 +- #6683: @dependabot[bot] chore(deps): bump importlib-metadata from 7.0.1 to 7.0.2 in /docs/_build +- #6684: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.0.5 to 42.0.6 + +## 0.33.8 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.8/bootstrap.sh +sudo DOKKU_TAG=v0.33.8 bash bootstrap.sh +``` + +### Bug Fixes + +- #6675: @josegonzalez Remove the need for executing crontab as root +- #6660: @josegonzalez Handle case where systemctl isn't in /usr/bin +- #6659: @josegonzalez Add missing cron:set command +- #6658: @josegonzalez Execute go get/build with mod in readonly mode +- #6642: @josegonzalez Use correct annotations key for the ingress chart +- #6631: @josegonzalez Correctly use cache volume for herokuish builds +- #6629: @josegonzalez Use smaller reference to image pull secrets in secret naming +- #6628: @josegonzalez Ensure non-web processes do not attempt to perform web logic in k3s templates +- #6623: @josegonzalez Do not allow reusing the same scheme:host-port mappings when setting ports +- #6624: @taraszka Install jq in vagrant vm +- #6614: @josegonzalez Correct issue where --force-tty was not properly supported by run:detached calls + +### New Features + +- #6673: @josegonzalez Add image version as label to built images +- #6640: @josegonzalez Add initial support for injecting keda addons +- #6662: @josegonzalez Allow limiting letsencrypt to certain domains when using openresty as a proxy +- #6643: @josegonzalez Add ability to add extra labels +- #6639: @josegonzalez Add support for setting underscores-in-headers for nginx, openresty, and k3s +- #6634: @josegonzalez feat: install keda addon in k3s cluster +- #6616: @josegonzalez Ensure referenced images get updated by dependabot +- #6613: @josegonzalez Add a helper binary to deploy a test app for local development + +### Refactors + +- #6644: @josegonzalez Only apply the cluster issuers helm chart when setting letsencrypt properties +- #6641: @josegonzalez Rename image pull secrets to standardize on secret naming pattern +- #6626: @josegonzalez Copy code into initial base image via docker build + +### Documentation + +- #6656: @holamendi Fix typo in builder management docs +- #6652: @Calyhre Added dokku-mdns to list of community plugins +- #6649: @undercontr Added detach flag to Docker installation documentation + +### Dependencies + +- #6671: @josegonzalez Update dokku dependencies +- #6672: @dependabot[bot] chore(deps): bump golang from 1.22.0 to 1.22.1 in /tests/apps/go-fail-predeploy +- #6670: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.62.0 to 1.62.1 in /tests/apps/gogrpc +- #6669: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.32.0 to 1.33.0 in /tests/apps/gogrpc +- #6668: @dependabot[bot] chore(deps): bump golang from 1.22.0 to 1.22.1 in /tests/apps/zombies-dockerfile-tini +- #6667: @dependabot[bot] chore(deps): bump golang from 1.22.0 to 1.22.1 in /tests/apps/gogrpc +- #6664: @dependabot[bot] chore(deps): bump golang from 1.22.0 to 1.22.1 in /tests/apps/go-fail-postdeploy +- #6666: @dependabot[bot] chore(deps): bump pyparsing from 3.1.1 to 3.1.2 in /docs/_build +- #6665: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.12 to 9.5.13 in /docs/_build +- #6663: @dependabot[bot] chore(deps): bump golang from 1.22.0 to 1.22.1 in /tests/apps/zombies-dockerfile-no-tini +- #6661: @josegonzalez Bump go modules +- #6654: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.7 to 10.7.1 in /docs/_build +- #6653: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.20.0 to 0.21.0 in /plugins/common +- #6651: @dependabot[bot] chore(deps): bump python-dateutil from 2.9.0 to 2.9.0.post0 in /docs/_build +- #6648: @dependabot[bot] chore(deps): bump python-dateutil from 2.8.2 to 2.9.0 in /docs/_build +- #6645: @dependabot[bot] chore(deps): bump rack from 2.2.8 to 2.2.8.1 in /tests/apps/ruby +- #6646: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.11 to 9.5.12 in /docs/_build +- #6638: @josegonzalez chore: bump go modules +- #6635: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.0.4 to 42.0.5 +- #6636: @dependabot[bot] chore(deps): bump timberio/vector from 0.36.X-debian to 0.36.0-debian in /plugins/logs +- #6637: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.19.0 to 0.20.0 in /plugins/common +- #6619: @dependabot[bot] chore(deps): bump traefik from v2.10 to 2.11 in /plugins/traefik-vhosts +- #6622: @josegonzalez chore(deps): bump timberio/vector from 0.35.X-debian to 0.36.X-debian in /plugins/logs +- #6618: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.10 to 9.5.11 in /docs/_build +- #6621: @dependabot[bot] chore(deps): bump byjg/easy-haproxy from 4.3.0 to 4.4.0 in /plugins/haproxy-vhosts +- #6610: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.14.1 to 3.14.2 in /plugins/scheduler-k3s + +## 0.33.7 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.7/bootstrap.sh +sudo DOKKU_TAG=v0.33.7 bash bootstrap.sh +``` + +### Bug Fixes + +- #6608: @josegonzalez Fix issue with setting k3s resource values and lower the initial default values +- #6607: @josegonzalez Call sed in unbuffered form to ensure log output is streamed to stdout +- #6600: @josegonzalez Use correct label key for worker nodes +- #6593: @josegonzalez Remove extra trailing newline to fix govet issue + +### New Features + +- #6595: @josegonzalez Add ability for users to specify alternative kubeconfig and kubecontext + +### Refactors + +- #6594: @josegonzalez Remove all calls to common.NewShellCmd +- #6592: @josegonzalez Remove all calls to common.NewShellCmdWithArgs +- #6591: @josegonzalez Always capture stdout/stderr when executing subprocesses +- #6590: @josegonzalez Remove all direct usage of go-sh outside of plugin trigger setup + +### Dependencies + +- #6609: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.61.1 to 1.62.0 in /tests/apps/gogrpc +- #6604: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.9 to 9.5.10 in /docs/_build +- #6603: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.0.2 to 42.0.4 +- #6601: @dependabot[bot] chore(deps): bump helm.sh/helm/v3 from 3.13.3 to 3.14.1 in /plugins/scheduler-k3s +- #6596: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.61.0 to 1.61.1 in /tests/apps/gogrpc + +## 0.33.6 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.6/bootstrap.sh +sudo DOKKU_TAG=v0.33.6 bash bootstrap.sh +``` + +### Bug Fixes + +- #6589: @josegonzalez Use image pull secrets instead of registries.yaml to reference private repositories when deploying via k3s +- #6581: @josegonzalez Fix issues in tls handling when deploying via k3s +- #6582: @josegonzalez Correctly handle extra whitespace in scale file contents +- #6390: @renweibo Prefer systemctl over sv when both exist while restarting nginx +- #6579: @josegonzalez Ensure k3s can be installed with taints + +### New Features + +- #6585: @josegonzalez Add more context to errors in fetching cron entries +- #6583: @josegonzalez Add WithContext functions for all subprocess-related code + +### Refactors + +- #6584: @josegonzalez Use new CallExecCommand when checking to see how help is being called + +### Dependencies + +- #6586: @dependabot[bot] chore(deps): bump mvdan.cc/sh/v3 from 3.7.0 to 3.8.0 in /plugins/cron +- #6587: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.8 to 9.5.9 in /docs/_build +- #6381: @dependabot[bot] chore(deps): bump actions/upload-artifact from 3 to 4 +- #6578: @josegonzalez Bump go modules + +### Other + +- #6576: @Tashows Allow creating a detached container with TTY enabled for attaching remote terminal + +## 0.33.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.5/bootstrap.sh +sudo DOKKU_TAG=v0.33.5 bash bootstrap.sh +``` + +### Bug Fixes + +- #6574: @josegonzalez Correct indentation when configuring https on a k3s app + +### Documentation + +- #6575: @josegonzalez Remove reference to web ui from digitalocean install documentation + +### Dependencies + +- #6573: @dependabot[bot] chore(deps): bump golang from 1.21.6 to 1.22.0 in /tests/apps/go-fail-postdeploy +- #6569: @dependabot[bot] chore(deps): bump golang from 1.21.6 to 1.22.0 in /tests/apps/go-fail-predeploy +- #6570: @dependabot[bot] chore(deps): bump golang.org/x/crypto from 0.18.0 to 0.19.0 in /plugins/common +- #6571: @dependabot[bot] chore(deps): bump golang from 1.21.6 to 1.22.0 in /tests/apps/zombies-dockerfile-tini +- #6572: @dependabot[bot] chore(deps): bump python from 3.12.1-alpine to 3.12.2-alpine in /docs/_build + +## 0.33.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.4/bootstrap.sh +sudo DOKKU_TAG=v0.33.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #6564: @josegonzalez Ignore link and volume flags for nixpacks builds +- #6563: @josegonzalez Wait until initial k3s node is ready before installing manifests +- #6561: @josegonzalez Index the process map to ensure the correct config is pulled +- #6560: @josegonzalez Skip app stop/delete when k3s is not installed +- #6559: @josegonzalez Check errors when writing properties +- #6556: @josegonzalez Respect the release namespace when creating resources for a k3s deploy + +### New Features + +- #6562: @josegonzalez Add support for specifying annotations on kubernetes resources +- #6555: @josegonzalez Add support for app-specific service accounts when deploying via k3s +- #6546: @josegonzalez Install kubectx and kubens helper binaries when setting up a k3s cluster + +### Refactors + +- #6558: @josegonzalez Use type-specific functions for writing contents to a file +- #6557: @josegonzalez Consolidate property fetching for nginx plugin into golang codebase +- #6536: @josegonzalez Manage vector container via compose + +### Documentation + +- #6545: @josegonzalez Migrate markdown doc rewriting into mkdocs hooks + +### Tests + +- #6554: @josegonzalez Fix issue where CI cannot install docker-buildx-plugin + +### Dependencies + +- #6568: @dependabot[bot] chore(deps): bump golang from 1.21.6 to 1.22.0 in /tests/apps/gogrpc +- #6567: @dependabot[bot] chore(deps): bump watchdog from 3.0.0 to 4.0.0 in /docs/_build +- #6566: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.7 to 9.5.8 in /docs/_build +- #6565: @dependabot[bot] chore(deps): bump golang from 1.21.6 to 1.22.0 in /tests/apps/zombies-dockerfile-no-tini +- #6551: @dependabot[bot] chore(deps): bump flask from 3.0.1 to 3.0.2 in /tests/apps/multi +- #6552: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.6 to 9.5.7 in /docs/_build +- #6553: @dependabot[bot] chore(deps): bump markupsafe from 2.1.4 to 2.1.5 in /docs/_build +- #6550: @dependabot[bot] chore(deps): bump flask from 3.0.1 to 3.0.2 in /tests/apps/python-flask +- #6548: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.19 to 11.0.20 in /tests/apps/java + +### Other + +- #6547: @josegonzalez Add support for nginx as a k3s ingress implementation + +## 0.33.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.3/bootstrap.sh +sudo DOKKU_TAG=v0.33.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #6542: @josegonzalez Drop -- when calling dokku run for cron-tab templating + +### Documentation + +- #6543: @josegonzalez Add support for badge shortcodes in documentation + +## 0.33.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.2/bootstrap.sh +sudo DOKKU_TAG=v0.33.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #6535: @josegonzalez Properly fetch default vector image if none is passed +- #6533: @josegonzalez Ensure all k3s traefik middleware resources are scoped to the process +- #6524: @josegonzalez Ensure all copied files always have line endings converted to unix-style + +### New Features + +- #6537: @josegonzalez Add the dokku logo as the chart icon +- #6527: @josegonzalez Upgrade vector image to 0.35.x +- #6534: @josegonzalez Allow specifying an ingress class via chart value + +### Documentation + +- #6530: @josegonzalez Fix indentation on letsencrypt sections for k3s scheduler docs + +### Tests + +- #6532: @josegonzalez Correct issues in tests for vector image log property + +### Dependencies + +- #6540: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.5 to 9.5.6 in /docs/_build +- #6538: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.0.1 to 42.0.2 +- #6528: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 42.0.0 to 42.0.1 +- #6529: @dependabot[bot] chore(deps): bump google.golang.org/protobuf from 1.31.0 to 1.32.0 in /tests/apps/gogrpc + +### Other + +- #6526: @josegonzalez Allow setting the vector image as a global property + +## 0.33.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.1/bootstrap.sh +sudo DOKKU_TAG=v0.33.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #6520: @josegonzalez Guard against missing healthcheck config +- #6519: @josegonzalez Use correct name for cluster-issuers helm chart + +### Refactors + +- #6521: @josegonzalez Install traefik via helm chart directly to avoid issues where traefik silently fails installation + +### Dependencies + +- #6523: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.4 to 9.5.5 in /docs/_build +- #6522: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.60.1 to 1.61.0 in /tests/apps/gogrpc + +## 0.33.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.33.0/bootstrap.sh +sudo DOKKU_TAG=v0.33.0 bash bootstrap.sh +``` + +### Bug Fixes + +- #6466: @josegonzalez Use correct key for migrating global scheduler configuration on upgrade +- #6450: @josegonzalez Always set -- on docker run commands internally to avoid parsing flags on specified commands +- #6447: @josegonzalez Propagate arm64 build support for development builds +- #6445: @josegonzalez Do not strip whitespace when displaying a file for debugging purposes +- #6442: @josegonzalez Ignore go.work when bumping modules +- #6427: @josegonzalez Add missing gitignore entry for app-json trigger + +### New Features + +- #6500: @josegonzalez Add support for custom user namespaces when creating persistent storage directories +- #6492: @josegonzalez Add support for multierror when exiting triggers +- #6491: @josegonzalez Add wrappers for exec and ssh commands +- #6489: @josegonzalez Disable flag parsing for dokku enter +- #6469: @josegonzalez Allow generating an ssh deploy key via the git plugin +- #6468: @josegonzalez Implement global support for setting proxy type +- #6467: @josegonzalez Add ability to customize the registry repo with a template +- #6464: @josegonzalez Create helper function for exposing a docker image's working directory +- #6462: @josegonzalez Ensure ps:stop has a proper cli header +- #6460: @josegonzalez Add a post-registry-login trigger +- #6459: @josegonzalez Alias common registry names to docker.io +- #6457: @josegonzalez Add the ability to specify the output format when listing ports +- #6452: @josegonzalez Add alternative implementation for calling plugin triggers +- #6446: @josegonzalez Add support for global-only environment properties +- #6443: @josegonzalez Add support for exposing healthchecks in the AppJson struct +- #6435: @josegonzalez Implement native k3s scheduler support +- #6433: @josegonzalez Add go.work.sum to gitignore +- #6432: @josegonzalez Upgrade golang to 1.21.6 +- #6430: @josegonzalez Reformat devcontainer file +- #6428: @josegonzalez Add ms-azuretools.vscode-docker to devcontainer setup +- #6313: @josegonzalez Set the platform flag in order to run amd64 images on arm64 deploy targets + +### Refactors + +- #6448: @josegonzalez Return an int32 for scale count +- #6444: @josegonzalez Allow setting a custom mode when writing a slice to a file + +### Documentation + +- #6517: @josegonzalez Remove k3s tutorial from documentation +- #6516: @josegonzalez Note that the external kubernetes plugin is deprecated +- #6515: @josegonzalez Replace the kubernetes plugin with k3s in the documentation +- #6514: @josegonzalez Add notes on k3s replacement of the scheduler-kubernetes plugin +- #6461: @josegonzalez Add documentation for implementing scheduler-related commands +- #6458: @josegonzalez Add docblocks to appjson structs +- #6420: @aochagavia Fix typo in zero downtime documentation + +### Tests + +- #6465: @josegonzalez Update haproxy tests so they pass +- #6449: @josegonzalez Run ci on ubuntu 22.04 +- #6431: @josegonzalez Add golanglint-ci testing support + +### Dependencies + +- #6513: @dependabot[bot] chore(deps): bump github.com/containerd/containerd from 1.7.6 to 1.7.11 in /plugins/scheduler-k3s +- #6512: @josegonzalez Update github.com/gonsi/gomega golang dependency +- #6511: @josegonzalez Update crypto and sys golang dependencies +- #6494: @dependabot[bot] chore(deps): bump markupsafe from 2.1.3 to 2.1.4 in /docs/_build +- #6482: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.3 to 9.5.4 in /docs/_build +- #6478: @dependabot[bot] chore(deps): bump golang from 1.21.5 to 1.21.6 in /tests/apps/zombies-dockerfile-tini +- #6456: @dependabot[bot] chore(deps): bump google.golang.org/grpc from 1.29.1 to 1.60.1 in /tests/apps/gogrpc +- #6454: @dependabot[bot] chore(deps): bump flask from 3.0.0 to 3.0.1 in /tests/apps/python-flask +- #6455: @dependabot[bot] chore(deps): bump flask from 3.0.0 to 3.0.1 in /tests/apps/multi +- #6453: @josegonzalez Remove ignored dependency from dependabot +- #6451: @josegonzalez Update go modules +- #6439: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.30.0 to 1.31.0 in /plugins/config +- #6438: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.30.0 to 1.31.0 in /plugins/common +- #6437: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.12.2 to 4.12.3 in /docs/_build +- #6436: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 41.1.1 to 42.0.0 +- #6425: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 41.0.1 to 41.1.1 +- #6424: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.3 to 9.5.4 in /docs/_build +- #6421: @josegonzalez chore: bump golang.org/x/sync from 0.5.0 to 0.6.0 +- #6419: @dependabot[bot] chore(deps): bump jinja2 from 3.1.2 to 3.1.3 in /docs/_build +- #6418: @dependabot[bot] chore(deps): bump jinja2 from 3.1.2 to 3.1.3 in /tests/apps/python-flask +- #6415: @dependabot[bot] chore(deps): bump golang from 1.21.5 to 1.21.6 in /tests/apps/go-fail-postdeploy +- #6414: @dependabot[bot] chore(deps): bump golang from 1.21.5 to 1.21.6 in /tests/apps/gogrpc +- #6413: @dependabot[bot] chore(deps): bump golang from 1.21.5 to 1.21.6 in /tests/apps/go-fail-predeploy +- #6412: @dependabot[bot] chore(deps): bump golang from 1.21.5 to 1.21.6 in /tests/apps/zombies-dockerfile-no-tini +- #6411: @dependabot[bot] chore(deps): bump golang from 1.21.5 to 1.21.6 in /tests/apps/zombies-dockerfile-tini +- #6410: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.19.0 to 0.20.0 in /tests/apps/gogrpc +- #6406: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.5.0 to 0.6.0 in /plugins/scheduler-docker-local + +### Other + +- #6463: @josegonzalez Use exported function from appjson module instead of manually parsing +- #6429: @josegonzalez Use go.work for development purposes + +## 0.32.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.32.4/bootstrap.sh +sudo DOKKU_TAG=v0.32.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #6407: @josegonzalez Upgrade ruby to fix the release process +- #6394: @josegonzalez Scope the delete of symlinked plugins in debian post-install to the plugin folders + +### Refactors + +- #6388: @Juneezee Replace deprecated `io/ioutil` functions + +### Documentation + +- #6385: @josegonzalez Fix docblock entries for properties in plugins +- #6384: @josegonzalez Cleanup some markdown in documentation +- #6365: @josegonzalez Split out archive/image deployment docs into their own file + +### Tests + +- #6408: @josegonzalez Add options to the release workflow +- #6359: @josegonzalez Update buildpack api for test buildpack + +### Dependencies + +- #6399: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.5 to 10.7 in /docs/_build +- #6398: @dependabot[bot] chore(deps): bump sinatra from 3.1.0 to 3.2.0 in /tests/apps/ruby +- #6395: @dependabot[bot] chore(deps): bump ruby from 3.2.2 to 3.3.0 in /tests/apps/dockerfile-entrypoint +- #6391: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.2 to 9.5.3 in /docs/_build +- #6392: @dependabot[bot] chore(deps): bump importlib-metadata from 7.0.0 to 7.0.1 in /docs/_build +- #6393: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 40.2.3 to 41.0.1 +- #6387: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.18 to 11.0.19 in /tests/apps/java +- #6386: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 40.2.2 to 40.2.3 +- #6377: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 40.2.1 to 40.2.2 +- #6380: @dependabot[bot] chore(deps): bump github/codeql-action from 2 to 3 +- #6379: @dependabot[bot] chore(deps): bump mkdocs-material from 9.5.1 to 9.5.2 in /docs/_build +- #6378: @dependabot[bot] chore(deps): bump python from 3.12.0-alpine to 3.12.1-alpine in /docs/_build +- #6376: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.14 to 9.5.1 in /docs/_build +- #6371: @dependabot[bot] chore(deps): bump golang from 1.21.4 to 1.21.5 in /tests/apps/gogrpc +- #6369: @dependabot[bot] chore(deps): bump golang from 1.21.4 to 1.21.5 in /tests/apps/zombies-dockerfile-tini +- #6368: @dependabot[bot] chore(deps): bump golang from 1.21.4 to 1.21.5 in /tests/apps/zombies-dockerfile-no-tini +- #6367: @dependabot[bot] chore(deps): bump importlib-metadata from 6.8.0 to 7.0.0 in /docs/_build +- #6366: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 40.2.0 to 40.2.1 +- #6370: @dependabot[bot] chore(deps): bump golang from 1.21.4 to 1.21.5 in /tests/apps/go-fail-predeploy +- #6372: @dependabot[bot] chore(deps): bump golang from 1.21.4 to 1.21.5 in /tests/apps/go-fail-postdeploy +- #6373: @dependabot[bot] chore(deps): bump actions/setup-python from 4 to 5 +- #6364: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.18.0 to 0.19.0 in /tests/apps/gogrpc +- #6361: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.4 to 10.5 in /docs/_build +- #6362: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.12 to 9.4.14 in /docs/_build +- #6360: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 40.1.1 to 40.2.0 +- #6357: @dependabot[bot] chore(deps): bump mkdocs-material-extensions from 1.3 to 1.3.1 in /docs/_build +- #6355: @dependabot[bot] chore(deps): bump pygments from 2.16.1 to 2.17.2 in /docs/_build +- #6358: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.8 to 9.4.12 in /docs/_build +- #6348: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.3.1 to 10.4 in /docs/_build + +## 0.32.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.32.3/bootstrap.sh +sudo DOKKU_TAG=v0.32.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #6345: @josegonzalez Use updated pre-build trigger for builder-herokuish + +### New Features + +- #6346: @josegonzalez Update all go modules and add a command to bump modules + +### Documentation + +- #6339: @nschlemm Fixed link to vector sink documentation +- #6314: @josegonzalez Remove reference to DOKKU_SCHEDULER environment variable in favor of scheduler:set +- #6317: @edmorley Replace deprecated builder reference in persistent-storage.md +- #6325: @josegonzalez Add documentation on openresty includes + +### Dependencies + +- #6343: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.17.0 to 0.18.0 in /tests/apps/gogrpc +- #6338: @dependabot[bot] chore(deps): bump golang from 1.21.3 to 1.21.4 in /tests/apps/go-fail-postdeploy +- #6337: @dependabot[bot] chore(deps): bump golang from 1.21.3 to 1.21.4 in /tests/apps/gogrpc +- #6336: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 40.0.2 to 40.1.1 +- #6335: @dependabot[bot] chore(deps): bump golang from 1.21.3 to 1.21.4 in /tests/apps/go-fail-predeploy +- #6334: @dependabot[bot] chore(deps): bump golang from 1.21.3 to 1.21.4 in /tests/apps/zombies-dockerfile-tini +- #6333: @dependabot[bot] chore(deps): bump golang from 1.21.3 to 1.21.4 in /tests/apps/zombies-dockerfile-no-tini +- #6328: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.7 to 9.4.8 in /docs/_build +- #6320: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.17 to 11.0.18 in /tests/apps/java +- #6318: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.6 to 9.4.7 in /docs/_build +- #6322: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 40.0.0 to 40.0.2 +- #6323: @dependabot[bot] chore(deps): bump django from 4.1.10 to 4.1.13 in /tests/apps/dockerfile-release + +### Other + +- #6315: @josegonzalez Correct key in dependency file to fix docker-container-healthchecker installs on local arm64 servers + +## 0.32.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.32.2/bootstrap.sh +sudo DOKKU_TAG=v0.32.2 bash bootstrap.sh +``` + +### New Features + +- #6324: @josegonzalez Update openresty image to 0.6.0 + +### Documentation + +- #6316: @holamendi Fix docs app-exists plugin trigger description + +### Dependencies + +- #6307: @dependabot[bot] chore(deps): bump luizm/action-sh-checker from 0.7.0 to 0.8.0 +- #6308: @dependabot[bot] chore(deps): bump werkzeug from 3.0.0 to 3.0.1 in /tests/apps/python-flask +- #6310: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.28.1 to 1.29.0 in /plugins/config +- #6311: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 39.2.4 to 40.0.0 + +## 0.32.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.32.1/bootstrap.sh +sudo DOKKU_TAG=v0.32.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #6286: @josegonzalez Remove trailing quote on openresty letsencrypt email +- #6287: @josegonzalez Recursively delete old custom openresty includes + +### New Features + +- #6303: @josegonzalez feat: add the ability to specify location-block includes + +### Documentation + +- #6288: @josegonzalez Add missing link to nixpacks builder + +### Dependencies + +- #6306: @dependabot[bot] chore(deps): bump org.apache.maven.plugins:maven-dependency-plugin from 3.6.0 to 3.6.1 in /tests/apps/java +- #6301: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.28.0 to 1.28.1 in /plugins/config +- #6304: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 39.2.3 to 39.2.4 +- #6305: @dependabot[bot] chore(deps): bump actions/setup-node from 3 to 4 +- #6297: @dependabot[bot] chore(deps): bump node from 20-alpine to 21-alpine in /tests/apps/dockerfile-procfile +- #6292: @dependabot[bot] chore(deps): bump node from 20-alpine to 21-alpine in /tests/apps/dockerfile-procfile-bad +- #6293: @dependabot[bot] chore(deps): bump node from 20-alpine to 21-alpine in /tests/apps/dockerfile +- #6294: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.3 to 10.3.1 in /docs/_build +- #6295: @dependabot[bot] chore(deps): bump node from 20-alpine to 21-alpine in /tests/apps/dockerfile-app-json-formations +- #6296: @dependabot[bot] chore(deps): bump node from 20-alpine to 21-alpine in /tests/apps/dockerfile-noexpose +- #6289: @dependabot[bot] chore(deps): bump mkdocs-material-extensions from 1.2 to 1.3 in /docs/_build +- #6290: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 39.2.2 to 39.2.3 + +## 0.32.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.32.0/bootstrap.sh +sudo DOKKU_TAG=v0.32.0 bash bootstrap.sh +``` + +See the [0.32.0 migration guide](/docs/appendices/0.32.0-migration-guide.md) for more information on migrating to 0.32.0. + +### New Features + +- #6277: @josegonzalez Run ps:restore in parallel by default +- #6276: @josegonzalez Warn when incorrect interface/port in use for web processes +- #6132: @josegonzalez Add the ability to specify a custom mailto for all cron output +- #6124: @josegonzalez Add a shell function to check if a plugin trigger exists +- #5348: @josegonzalez Add nixpacks builder support + +### Removals + +- #6283: @josegonzalez Remove deprecated proxy-ports functions and and plugin subcommands + +### Deprecations + +- #6127: @josegonzalez Deprecate the builder-specific pre-release-* triggers in favor of a global pre-release-builder trigger +- #6126: @josegonzalez Deprecate the builder-specific post-build-* triggers in favor of a global post-build trigger +- #6125: @josegonzalez Deprecate the builder-specific pre-build-* triggers in favor of a global pre-build trigger + +### Documentation + +- #6284: @josegonzalez Document future removal of deprecated CHECKS file format +- #6123: @josegonzalez Add a migration guide for 0.32.x + +### Dependencies + +- #6285: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.5 to 9.4.6 in /docs/_build +- #6281: @josegonzalez Upgrade golang to 1.21 + +### Other + +- #6155: @josegonzalez Remove ARM support + +## 0.31.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.31.5/bootstrap.sh +sudo DOKKU_TAG=v0.31.5 bash bootstrap.sh +``` + +### Bug Fixes + +- #6275: @josegonzalez Update message referencing CHECKS file usage to app.json +- #6273: @josegonzalez Clean up local build images immediately after an image is released + +### New Features + +- #6274: @josegonzalez Add more version output to dokku report command +- #6255: @josegonzalez Add ci concurrency to linting workflow +- #6253: @josegonzalez Add ci concurrency to doc building +- #6254: @josegonzalez Add ci concurrency to codeql analysis +- #6222: @josegonzalez Install docker-buildx-plugin to silence buildx warnings + +### Refactors + +- #6257: @josegonzalez Manage Dokku system dependencies in a single file + +### Documentation + +- #6271: @josegonzalez Update list of official, community, and deprecated plugins +- #6224: @joeyates Correct typo in example app.json for healthchecks +- #6207: @AlejandroAkbal Update port clearing command in Dockerfile deploy documentation + +### Tests + +- #6278: @josegonzalez Remove pack installation from builder-lambda tests + +### Dependencies + +- #6269: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.14.0 to 0.17.0 in /plugins/common +- #6270: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 39.2.1 to 39.2.2 +- #6262: @dependabot[bot] chore(deps): bump golang from 1.21.2 to 1.21.3 in /tests/apps/zombies-dockerfile-no-tini +- #6263: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.16.0 to 0.17.0 in /tests/apps/gogrpc +- #6264: @dependabot[bot] chore(deps): bump golang from 1.21.2 to 1.21.3 in /tests/apps/zombies-dockerfile-tini +- #6265: @dependabot[bot] chore(deps): bump golang from 1.21.2 to 1.21.3 in /tests/apps/go-fail-predeploy +- #6259: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.16 to 11.0.17 in /tests/apps/java +- #6260: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.4 to 9.4.5 in /docs/_build +- #6261: @dependabot[bot] chore(deps): bump golang from 1.21.2 to 1.21.3 in /tests/apps/gogrpc +- #6266: @dependabot[bot] chore(deps): bump golang from 1.21.2 to 1.21.3 in /tests/apps/go-fail-postdeploy +- #6258: @dependabot[bot] chore(deps): update markdown requirement from <3.5,>=3.2.1 to >=3.2.1,<3.6 in /docs/_build +- #6256: @josegonzalez chore: bump github.com/otiai10/copy and golang.org/x/sync +- #6241: @dependabot[bot] chore(deps): bump python from 3.11.5-alpine to 3.12.0-alpine in /docs/_build +- #6252: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.2 to 9.4.4 in /docs/_build +- #6235: @dependabot[bot] chore(deps): bump packaging from 23.1 to 23.2 in /docs/_build +- #6232: @dependabot[bot] chore(deps): bump werkzeug from 2.3.7 to 3.0.0 in /tests/apps/python-flask +- #6231: @dependabot[bot] chore(deps): bump flask from 2.3.3 to 3.0.0 in /tests/apps/python-flask +- #6248: @dependabot[bot] chore(deps): bump golang from 1.21.1 to 1.21.2 in /tests/apps/gogrpc +- #6251: @dependabot[bot] chore(deps): bump golang from 1.21.1 to 1.21.2 in /tests/apps/zombies-dockerfile-tini +- #6250: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.15.0 to 0.16.0 in /tests/apps/gogrpc +- #6249: @dependabot[bot] chore(deps): bump golang from 1.21.1 to 1.21.2 in /tests/apps/go-fail-predeploy +- #6247: @dependabot[bot] chore(deps): bump golang from 1.21.1 to 1.21.2 in /tests/apps/zombies-dockerfile-no-tini +- #6246: @dependabot[bot] chore(deps): bump golang from 1.21.1 to 1.21.2 in /tests/apps/go-fail-postdeploy +- #6245: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.3.0 to 0.4.0 in /plugins/scheduler-docker-local +- #6233: @dependabot[bot] chore(deps): bump flask from 2.3.3 to 3.0.0 in /tests/apps/multi +- #6239: @dependabot[bot] chore(deps): bump github.com/otiai10/copy from 1.12.0 to 1.14.0 in /plugins/ps +- #6234: @dependabot[bot] chore(deps): bump github.com/otiai10/copy from 1.12.0 to 1.14.0 in /plugins/builder +- #6240: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 39.2.0 to 39.2.1 +- #6228: @josegonzalez Update all go modules and ensure all are tracked in dependabot +- #6227: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.27.10 to 1.28.0 in /plugins/common +- #6216: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 39.1.2 to 39.2.0 +- #6217: @dependabot[bot] chore(deps): bump mkdocs-material from 9.4.1 to 9.4.2 in /docs/_build +- #6215: @dependabot[bot] chore(deps): bump mkdocs-material from 9.3.1 to 9.4.1 in /docs/_build +- #6214: @dependabot[bot] chore(deps): bump mkdocs-material-extensions from 1.1.1 to 1.2 in /docs/_build +- #6209: @dependabot[bot] chore(deps): bump zipp from 3.16.2 to 3.17.0 in /docs/_build +- #6211: @dependabot[bot] chore(deps): bump mkdocs from 1.5.2 to 1.5.3 in /docs/_build +- #6213: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 39.0.2 to 39.1.2 +- #6202: @dependabot[bot] chore(deps): bump docker/setup-buildx-action from 2 to 3 +- #6199: @dependabot[bot] chore(deps): bump docker/login-action from 2 to 3 +- #6200: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 39.0.0 to 39.0.2 +- #6201: @dependabot[bot] chore(deps): bump docker/setup-qemu-action from 2 to 3 +- #6203: @dependabot[bot] chore(deps): bump mkdocs-material from 9.2.8 to 9.3.1 in /docs/_build + +## 0.31.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.31.4/bootstrap.sh +sudo DOKKU_TAG=v0.31.4 bash bootstrap.sh +``` + +### Documentation + +- #5958: @gamedevsam Update command used to retrieve image digest when image is not pushed to a registry + +### Dependencies + +- #6194: @dependabot[bot] chore(deps): bump golang from 1.21.0 to 1.21.1 in /tests/apps/go-fail-predeploy +- #6197: @dependabot[bot] chore(deps): bump golang from 1.21.0 to 1.21.1 in /tests/apps/gogrpc +- #6195: @dependabot[bot] chore(deps): bump golang from 1.21.0 to 1.21.1 in /tests/apps/go-fail-postdeploy +- #6196: @dependabot[bot] chore(deps): bump golang from 1.21.0 to 1.21.1 in /tests/apps/zombies-dockerfile-tini +- #6193: @dependabot[bot] chore(deps): bump golang from 1.21.0 to 1.21.1 in /tests/apps/zombies-dockerfile-no-tini +- #6190: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.14.0 to 0.15.0 in /tests/apps/gogrpc + +### Other + +- #6191: @josegonzalez chore: remove debug logging from git plugin + +## 0.31.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.31.3/bootstrap.sh +sudo DOKKU_TAG=v0.31.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #6179: @rct-k Fix build cache for herokuish-built apps + +### Tests + +- #6177: @maxvisser Update shellcheck junit integration for newer version of shellcheck + +### Dependencies + +- #6186: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.2.1 to 10.3 in /docs/_build +- #6184: @dependabot[bot] chore(deps): bump soupsieve from 2.4.1 to 2.5 in /docs/_build +- #6183: @dependabot[bot] chore(deps): bump actions/checkout from 3 to 4 +- #6187: @dependabot[bot] chore(deps): bump mkdocs-material from 9.2.6 to 9.2.8 in /docs/_build +- #6188: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 38.2.0 to 39.0.0 +- #6173: @dependabot[bot] chore(deps): bump org.eclipse.jetty:jetty-servlet from 11.0.15 to 11.0.16 in /tests/apps/java +- #6174: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 38.1.3 to 38.2.0 +- #6175: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.2 to 10.2.1 in /docs/_build +- #6176: @dependabot[bot] chore(deps): bump mkdocs-material from 9.2.5 to 9.2.6 in /docs/_build +- #6171: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.1 to 10.2 in /docs/_build +- #6168: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 38.1.0 to 38.1.3 +- #6170: @dependabot[bot] chore(deps): bump mkdocs-material from 9.2.3 to 9.2.5 in /docs/_build +- #6169: @dependabot[bot] chore(deps): bump python from 3.11.4-alpine to 3.11.5-alpine in /docs/_build + +## 0.31.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.31.2/bootstrap.sh +sudo DOKKU_TAG=v0.31.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #6154: @josegonzalez Ensure app clones have domains setup +- #6164: @josegonzalez Use correct path for openresty letsencrypt data +- #6165: @josegonzalez Ensure we cleanup extracted files prior to next deployment +- #6166: @josegonzalez Remove unused source from openresty trigger +- #6163: @josegonzalez Add missing triggers for openresty-vhosts plugin +- #6151: @josegonzalez Do not pass an empty argument to scheduler-run when triggering cron tasks manually +- #6150: @josegonzalez Correct issue where temp file cleanup fails deploy + +### Documentation + +- #6167: @josegonzalez Correct svg path on homepage +- #6153: @imankulov Fix superscript typos in plugin documentation + +### Tests + +- #6152: @josegonzalez Use buildjet for building arm images +- #6149: @josegonzalez Fix issue where CI cannot install docker-compose-plugin + +### Dependencies + +- #6161: @josegonzalez Upgrade sigil to 0.10.1 +- #6162: @josegonzalez Upgrade herokuish to 0.7.1 +- #6160: @josegonzalez Upgrade sshcommand to 0.17.1 +- #6159: @josegonzalez Upgrade procfile-util to 0.16.0 +- #6158: @josegonzalez Upgrade netrc to 0.7.1 +- #6157: @josegonzalez Upgrade lambda-builder to 0.5.0 +- #6147: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.6.1 to 38.1.0 +- #6145: @dependabot[bot] chore(deps): bump mkdocs-material from 9.2.2 to 9.2.3 in /docs/_build +- #6139: @dependabot[bot] chore(deps): bump flask from 2.3.2 to 2.3.3 in /tests/apps/multi +- #6140: @dependabot[bot] chore(deps): bump flask from 2.3.2 to 2.3.3 in /tests/apps/python-flask +- #6141: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.21 to 9.2.2 in /docs/_build + +### Other + +- #6148: @josegonzalez Add missing relabel command to docker-image-labeler for CNB builder + +## 0.31.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.31.1/bootstrap.sh +sudo DOKKU_TAG=v0.31.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #6133: @josegonzalez Properly handle tag and branch pushes +- #6131: @josegonzalez Ensure tmp files are cleaned up when commands exit +- #6130: @josegonzalez Correct package for container healthchecker when building digitalocean image +- #6121: @josegonzalez Add missing error-log-path function to openresty plugin + +### Documentation + +- #6129: @josegonzalez Cleanup markdown and use Github Admonitions + +### Dependencies + +- #6120: @dependabot[bot] chore(deps): bump click from 8.1.6 to 8.1.7 in /docs/_build +- #6119: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.6.0 to 37.6.1 +- #6118: @dependabot[bot] chore(deps): bump werkzeug from 2.3.6 to 2.3.7 in /tests/apps/python-flask +- #6115: @dependabot[bot] chore(deps): bump golang from 1.20.7 to 1.21.0 in /tests/apps/zombies-dockerfile-no-tini +- #6114: @dependabot[bot] chore(deps): bump golang from 1.20.7 to 1.21.0 in /tests/apps/zombies-dockerfile-tini +- #6104: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.13.0 to 0.14.0 in /tests/apps/gogrpc +- #6113: @dependabot[bot] chore(deps): bump golang from 1.20.7 to 1.21.0 in /tests/apps/go-fail-predeploy +- #6112: @dependabot[bot] chore(deps): bump golang from 1.20.7 to 1.21.0 in /tests/apps/go-fail-postdeploy +- #6111: @dependabot[bot] chore(deps): bump golang from 1.20.7 to 1.21.0 in /tests/apps/gogrpc +- #6107: @dependabot[bot] chore(deps): bump pygments from 2.15.1 to 2.16.1 in /docs/_build +- #6106: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.5.2 to 37.6.0 +- #6105: @dependabot[bot] chore(deps): bump sinatra from 3.0.6 to 3.1.0 in /tests/apps/ruby + +### Other + +- #6122: @josegonzalez Plugin trigger and event cleanup + +## 0.31.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.31.0/bootstrap.sh +sudo DOKKU_TAG=v0.31.0 bash bootstrap.sh +``` + +See the [0.31.0 migration guide](/docs/appendices/0.31.0-migration-guide.md) for more information on migrating to 0.31.0. + +### Backwards Compatibility Breaks + +- #6102: @josegonzalez Deprecate ARM as a build target +- #6029: @josegonzalez Provide users the ability to override auto-detected port mappings + +### Bug Fixes + +- #6110: @josegonzalez Correctly handle rebase when generating release notes for minor/major releases +- #6081: @josegonzalez Use correct namespace for haproxy service state tracking +- #6079: @josegonzalez Remove debugging code from builder-dockerfile plugin +- #6078: @josegonzalez Use correct quoting in format template when fetching exposed image ports +- #6070: @josegonzalez Do not write empty values for nginx container labels +- #6014: @josegonzalez Automatically clear the git source-image property when changing deployment methodologies +- #5985: @josegonzalez Mount the traefik data directory instead of the acme.json file when starting traefik +- #5979: @josegonzalez Do not start proxy implementations during server restore if they weren't manually started via the :start command +- #5973: @josegonzalez Remove all containers and images by label on app destroy + +### New Features + +- #6100: @josegonzalez Add support for non-web healthchecks via app.json +- #6098: @josegonzalez Upgrade traefik image from v2.8 to v2.10 +- #6097: @josegonzalez Upgrade caddy image from 2.7 to 2.8 +- #6082: @josegonzalez Add openresty proxy implementation +- #6057: @josegonzalez Inject docker labels when nginx proxy implementation is in use +- #6043: @josegonzalez Write auto-detected port mappings during a deploy +- #6013: @josegonzalez Export environment variables during dockerfile builds for use with value-less --build-arg flags +- #6007: @Firfi Add application/wasm to nginx.conf.sigil gzip +- #5993: @josegonzalez Un-deprecate apps and config cli aliases +- #5992: @josegonzalez Mount the vector data directory instead of the vector file +- #5991: @josegonzalez Add the ability to execute a cron task on the fly +- #5990: @josegonzalez Add json format output to cron:list command +- #5989: @josegonzalez Skip scaled processes that are missing in the Procfile +- #5978: @josegonzalez Export environment variables during dockerfile builds for use with value-less --build-arg flags +- #5908: @josegonzalez Generate jobs for crontab in parallel +- #5891: @josegonzalez Add support for specifying multiple networks on a given app + +### Refactors + +- #6042: @josegonzalez Rename port-map property to port +- #6021: @josegonzalez Simplify ports-configure codebase +- #6018: @josegonzalez Move code that fetches raw tcp ports for dockerfile deploys to ports plugin +- #6017: @josegonzalez Deprecate proxy-configure-ports plugin trigger in favor of ports-configure +- #6011: @josegonzalez Use ports-get plugn trigger for fetching port mappings +- #6010: @josegonzalez Use ports-clear plugn trigger to manage clearing the port map +- #5988: @josegonzalez Move crontab writing code to scheduler-docker-local plugin +- #5975: @josegonzalez Standardize on shorthand for redirecting all output to /dev/null +- #5974: @josegonzalez Standardize on ls subcommand when interacting with the docker binary + +### Documentation + +- #6116: @josegonzalez Remove extra newline in migration docs +- #6099: @josegonzalez Clean up references in proxy plugins +- #5987: @josegonzalez Clarify that a branch can be specified when updating a plugin + +### Tests + +- #6103: @josegonzalez Timeout docker image builds in CI +- #6101: @josegonzalez Make it possible to specify an alternative base domain for tests +- #6095: @josegonzalez Remove assertion for unconsumed build arguments +- #6094: @josegonzalez Correct assertion for dockerfile builds when a variable is eval'd +- #6080: @josegonzalez Add assertions to various bats tests +- #6076: @josegonzalez Use assert_output_not_exists instead of asserting output is empty string +- #6075: @josegonzalez Use an alternative curl request to ensure requests go to the local nginx +- #6074: @josegonzalez Fix issue where networks weren't being torn down during testing +- #6012: @josegonzalez Use ports:report to get the list of port mappings +- #5977: @josegonzalez Move all shellcheck disable definitions to .shellcheckrc +- #5976: @josegonzalez Remove Stickler configuration + +### Other + +- #6109: @josegonzalez Make heroku-22/jammy the default stack for cnb/herokuish builds +- #6096: @josegonzalez Upgrade vector image from 0.23.x to 0.31.x +- #6019: @josegonzalez Move code for fetching an available port to the ports plugin +- #6015: @josegonzalez Move CHECKS file extraction to the beginning of the deploy +- #5995: @josegonzalez Migrate the proxy port map from config variable to property system +- #5986: @josegonzalez Move all port management code to standalone ports plugin +- #5495: @josegonzalez Move herokuish app cache from the filesystem into a docker volume + +## 0.30.11 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.11/bootstrap.sh +sudo DOKKU_TAG=v0.30.11 bash bootstrap.sh +``` + +### Dependencies + +- #6091: @dependabot[bot] chore(deps): bump mkdocs from 1.5.1 to 1.5.2 in /docs/_build +- #6093: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.5.1 to 37.5.2 +- #6088: @dependabot[bot] chore(deps): bump golang from 1.20.6 to 1.20.7 in /tests/apps/gogrpc +- #6087: @dependabot[bot] chore(deps): bump golang from 1.20.6 to 1.20.7 in /tests/apps/zombies-dockerfile-no-tini +- #6086: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.12.0 to 0.13.0 in /tests/apps/gogrpc +- #6085: @dependabot[bot] chore(deps): bump golang from 1.20.6 to 1.20.7 in /tests/apps/go-fail-postdeploy +- #6084: @dependabot[bot] chore(deps): bump golang from 1.20.6 to 1.20.7 in /tests/apps/go-fail-predeploy +- #6083: @dependabot[bot] chore(deps): bump golang from 1.20.6 to 1.20.7 in /tests/apps/zombies-dockerfile-tini +- #6072: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.5.0 to 37.5.1 +- #6073: @dependabot[bot] chore(deps): bump pyparsing from 3.1.0 to 3.1.1 in /docs/_build + +## 0.30.10 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.10/bootstrap.sh +sudo DOKKU_TAG=v0.30.10 bash bootstrap.sh +``` + +### Bug Fixes + +- #6036: @l3x4 Remove auth middleware from traefik compose setup when basic auth is not enabled +- #6041: @josegonzalez Use updated path for devcontainer vscode extensions +- #6022: @iphoting Fix issue with docker plugin-list install failing boot for docker-based installations +- #6028: @josegonzalez Detect and use systemd on Debian systems when interacting with nginx + +### Documentation + +- #6068: @nickgal Fix typo in port management docs +- #6063: @PabloCastellano Fix typo in nginx docs + +### Tests + +- #6035: @iphoting Allow the check-commit CI job to work with PRs from other repos + +### Dependencies + +- #6065: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.19 to 9.1.21 in /docs/_build +- #6064: @dependabot[bot] chore(deps): bump mkdocs from 1.4.3 to 1.5.1 in /docs/_build +- #6059: @josegonzalez Update go packages to fix build issues +- #6058: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.4.0 to 37.5.0 +- #6053: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.3.0 to 37.4.0 +- #6049: @dependabot[bot] chore(deps): bump gunicorn from 21.1.0 to 21.2.0 in /tests/apps/multi +- #6050: @dependabot[bot] chore(deps): bump click from 8.1.5 to 8.1.6 in /docs/_build +- #6051: @dependabot[bot] chore(deps): bump gunicorn from 21.1.0 to 21.2.0 in /tests/apps/python-flask +- #6052: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.1.2 to 37.3.0 +- #6048: @dependabot[bot] chore(deps): bump pyyaml from 6.0 to 6.0.1 in /docs/_build +- #6047: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.18 to 9.1.19 in /docs/_build +- #6046: @dependabot[bot] chore(deps): bump gunicorn from 20.1.0 to 21.1.0 in /tests/apps/python-flask +- #6045: @dependabot[bot] chore(deps): bump gunicorn from 20.1.0 to 21.1.0 in /tests/apps/multi +- #6044: @dependabot[bot] chore(deps): bump zipp from 3.16.1 to 3.16.2 in /docs/_build +- #6038: @dependabot[bot] chore(deps): bump pymdown-extensions from 10.0.1 to 10.1 in /docs/_build +- #6039: @dependabot[bot] chore(deps): bump click from 8.1.4 to 8.1.5 in /docs/_build +- #6040: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.1.1 to 37.1.2 +- #6037: @dependabot[bot] chore(deps): bump zipp from 3.16.0 to 3.16.1 in /docs/_build +- #6034: @dependabot[bot] chore(deps): bump golang from 1.20.5 to 1.20.6 in /tests/apps/zombies-dockerfile-tini +- #6032: @dependabot[bot] chore(deps): bump golang from 1.20.5 to 1.20.6 in /tests/apps/zombies-dockerfile-no-tini +- #6033: @dependabot[bot] chore(deps): bump golang from 1.20.5 to 1.20.6 in /tests/apps/gogrpc +- #6031: @dependabot[bot] chore(deps): bump golang from 1.20.5 to 1.20.6 in /tests/apps/go-fail-postdeploy +- #6030: @dependabot[bot] chore(deps): bump golang from 1.20.5 to 1.20.6 in /tests/apps/go-fail-predeploy +- #6027: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.1.0 to 37.1.1 +- #6024: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.0.5 to 37.1.0 +- #6026: @dependabot[bot] chore(deps): bump importlib-metadata from 6.7.0 to 6.8.0 in /docs/_build +- #6025: @dependabot[bot] chore(deps): bump zipp from 3.15.0 to 3.16.0 in /docs/_build + +### Other + +- #6056: @Coffee2CodeNL Add debian bookworm to release and bootstrap script + +## 0.30.9 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.9/bootstrap.sh +sudo DOKKU_TAG=v0.30.9 bash bootstrap.sh +``` + +### Bug Fixes + +- #6009: @josegonzalez Properly set the plugin repository name when installing in a docker-based dokku install +- #5999: @caplod Fix http to https redirect in traefik + +### Documentation + +- #6006: @josegonzalez Clarify how to specify the contents of an ssh key for dokku +- #5998: @kleutzinger Fix typo in scheduled cron task documentation +- #5984: @josegonzalez Unify all Procfile-handling documentation under the process management docs +- #5982: @josegonzalez Update docs to mention that files are extracted from source where source code is available + +### Dependencies + +- #6008: @dependabot[bot] chore(deps): bump click from 8.1.3 to 8.1.4 in /docs/_build +- #6003: @dependabot[bot] chore(deps): bump django from 4.1.9 to 4.1.10 in /tests/apps/dockerfile-release +- #6005: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.11.0 to 0.12.0 in /tests/apps/gogrpc +- #6000: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.17 to 9.1.18 in /docs/_build + +## 0.30.8 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.8/bootstrap.sh +sudo DOKKU_TAG=v0.30.8 bash bootstrap.sh +``` + +### Bug Fixes + +- #5941: @josegonzalez Use github token to avoid rate limiting during packer init +- #5930: @michaelmulley Fix issue with docker plugin-list install failing boot for docker-based installations + +### New Features + +- #5962: @jaytula Add Debian 12 support to the bootstrap install script +- #5926: @josegonzalez Update default herokuish version to 0.6.0 + +### Documentation + +- #5943: @josegonzalez Add a note for each network type mentioning when they are best used +- #5938: @josegonzalez Add build-base to ensure gcc is available +- #5927: @aradalvand Improve docs about `X-Forwarded-*` headers and move it to nginx.md +- #5921: @aradalvand Update dockerfiles.md to mention that BuildKit is the default builder from Docker v24 onwards +- #5923: @aradalvand Remove `:master` from `git push` commands in the documentation + +### Tests + +- #5972: @josegonzalez Add permissions to allow publishing test results for pull requests +- #5944: @josegonzalez Add the ability to skip ci when commit message includes a ci skip message or are docs related + +### Dependencies + +- #5971: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.0.4 to 37.0.5 +- #5967: @dependabot[bot] chore(deps): bump socket.io from 4.6.2 to 4.7.1 in /tests/apps/.websocket.disabled +- #5968: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 37.0.3 to 37.0.4 +- #5966: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.16 to 9.1.17 in /docs/_build +- #5965: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.4.1 to 37.0.3 +- #5957: @josegonzalez chore: update go packages to fix build issues +- #5956: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.4.0 to 36.4.1 +- #5949: @dependabot[bot] chore(deps): bump github.com/otiai10/copy from 1.11.0 to 1.12.0 in /plugins/builder +- #5951: @dependabot[bot] chore(deps): bump github.com/otiai10/copy from 1.11.0 to 1.12.0 in /plugins/ps +- #5952: @dependabot[bot] chore(deps): bump github.com/otiai10/copy from 1.11.0 to 1.12.0 in /plugins/common +- #5953: @dependabot[bot] chore(deps): bump github.com/otiai10/copy from 1.11.0 to 1.12.0 in /plugins/app-json +- #5948: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.3.0 to 36.4.0 +- #5950: @dependabot[bot] chore(deps): bump importlib-metadata from 6.6.0 to 6.7.0 in /docs/_build +- #5954: @dependabot[bot] chore(deps): bump pyparsing from 3.0.9 to 3.1.0 in /docs/_build +- #5946: @josegonzalez Update to actions/checkout@v3 +- #5940: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.15 to 9.1.16 in /docs/_build +- #5939: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.2.1 to 36.3.0 +- #5937: @josegonzalez chore: update go packages to fix build issues +- #5934: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.2.0 to 0.3.0 in /plugins/app-json +- #5933: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.1.0 to 36.2.1 +- #5935: @dependabot[bot] chore(deps): bump golang.org/x/sync from 0.2.0 to 0.3.0 in /plugins/common +- #5928: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.10.0 to 0.11.0 in /tests/apps/gogrpc +- #5922: @dependabot[bot] chore(deps): bump werkzeug from 2.3.5 to 2.3.6 in /tests/apps/python-flask +- #5919: @dependabot[bot] chore(deps): bump python from 3.11.3-buster to 3.11.4-buster in /tests/apps/dockerfile-release +- #5913: @dependabot[bot] chore(deps): bump golang from 1.20.4 to 1.20.5 in /tests/apps/go-fail-predeploy +- #5915: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.27.7 to 1.27.8 in /plugins/config +- #5916: @dependabot[bot] chore(deps): bump github.com/onsi/gomega from 1.27.7 to 1.27.8 in /plugins/common +- #5917: @dependabot[bot] chore(deps): bump werkzeug from 2.3.4 to 2.3.5 in /tests/apps/python-flask +- #5918: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.18 to 36.1.0 +- #5920: @dependabot[bot] chore(deps): bump python from 3.11.3-alpine to 3.11.4-alpine in /docs/_build +- #5914: @dependabot[bot] chore(deps): bump golang from 1.20.4 to 1.20.5 in /tests/apps/zombies-dockerfile-no-tini +- #5912: @dependabot[bot] chore(deps): bump golang from 1.20.4 to 1.20.5 in /tests/apps/go-fail-postdeploy +- #5911: @dependabot[bot] chore(deps): bump golang from 1.20.4 to 1.20.5 in /tests/apps/gogrpc +- #5910: @dependabot[bot] chore(deps): bump golang from 1.20.4 to 1.20.5 in /tests/apps/zombies-dockerfile-tini +- #5909: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.17 to 36.0.18 +- #5906: @dependabot[bot] chore(deps): bump markupsafe from 2.1.2 to 2.1.3 in /docs/_build +- #5907: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.15 to 36.0.17 + +### Other + +- #5945: @josegonzalez tests: split out image building into it's own job to speed up ci +- #5942: @josegonzalez Move the times function to functions.go + +## 0.30.7 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.7/bootstrap.sh +sudo DOKKU_TAG=v0.30.7 bash bootstrap.sh +``` + +### Bug Fixes + +- #5905: @josegonzalez fix: trim whitespace on registry property values + +### Documentation + +- #5897: @josegonzalez Add cron-restart to plugin registry + +### Dependencies + +- #5904: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.11 to 36.0.15 +- #5902: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.10 to 36.0.11 +- #5900: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.9 to 36.0.10 +- #5898: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.8 to 36.0.9 +- #5899: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.14 to 9.1.15 in /docs/_build +- #5894: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.6 to 36.0.8 + +## 0.30.6 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.6/bootstrap.sh +sudo DOKKU_TAG=v0.30.6 bash bootstrap.sh +``` + +### Bug Fixes + +- #5889: @josegonzalez Respect dashboard disabling properties in traefik +- #5890: @josegonzalez Add missing report triggers and omitted proxy functionality +- #5757: @josegonzalez Ensure users can push from an image-deploy repository and respect deploy-branch + +### New Features + +- #5873: @josegonzalez Add dependabot to all plugin dependencies + +### Dependencies + +- #5877: @dependabot[bot] chore(deps): bump github.com/multiformats/go-base36 from 0.1.1-0.20220823151017-f5af2eed4d9c to 0.2.0 in /plugins/cron +- #5886: @josegonzalez chore: bump github.com/otiai10/copy from 1.9.0 to 1.11.0 +- #5875: @dependabot[bot] chore(deps): bump github.com/gofrs/flock from 0.8.0 to 0.8.1 in /plugins/ps +- #5885: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 36.0.3 to 36.0.6 +- #5871: @dependabot[bot] chore(deps): bump tj-actions/changed-files from 35.9.2 to 36.0.3 + +### Other + +- #5887: @josegonzalez chore: bump github.com/ryanuber/columnize from 1.1.2-0.20190319233515-9e6335e58db3 to 2.1.2+incompatible +- #5872: @Samuelodan Fix typo in upgrading docs + +## 0.30.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.5/bootstrap.sh +sudo DOKKU_TAG=v0.30.5 bash bootstrap.sh +``` + +### New Features + +- #5862: @edmorley Stop using the `latest` tag for the default `DOKKU_CNB_BUILDER` + +### Documentation + +- #5870: @josegonzalez Repair malformed code-block +- #5863: @joker234 Remove reference to ssh github clone from app deployment docs + +### Tests + +- #5867: @josegonzalez Update linuxserver image used in tests to one that is compatible with arm64 + +### Dependencies + +- #5868: @josegonzalez Update all golang dependencies to fix go.0 tests +- #5856: @dependabot[bot] chore(deps): bump pymdown-extensions from 9.11 to 10.0.1 in /docs/_build +- #5865: @dependabot[bot] chore(deps): bump requests from 2.27.1 to 2.31.0 in /tests/apps/lambda-python +- #5864: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.12 to 9.1.14 in /docs/_build +- #5866: @dependabot[bot] chore(deps): bump maven-dependency-plugin from 3.5.0 to 3.6.0 in /tests/apps/java + +## 0.30.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.4/bootstrap.sh +sudo DOKKU_TAG=v0.30.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #5786: @obrienmd Detect id_ed25519.pub in git:public-key command +- #5758: @josegonzalez Support pack-based images that do not have a Procfile + +### New Features + +- #5851: @josegonzalez Allow setting _ as domain name for default nginx routing + +### Documentation + +- #5853: @josegonzalez Always overwrite bootstrap install file +- #5828: @josegonzalez Add a note to the troubleshooting docs pointing developers to bind to all interfaces +- #5827: @josegonzalez Add permalink support to headers + +### Tests + +- #5850: @josegonzalez Only run packer validation if any packer-related files were changed + +### Dependencies + +- #5837: @dependabot[bot] chore(deps): bump golang from 1.20.3 to 1.20.4 in /tests/apps/zombies-dockerfile-tini +- #5836: @dependabot[bot] chore(deps): bump golang from 1.20.3 to 1.20.4 in /tests/apps/gogrpc +- #5832: @dependabot[bot] chore(deps): bump flask from 2.3.1 to 2.3.2 in /tests/apps/multi +- #5831: @dependabot[bot] chore(deps): bump flask from 2.3.1 to 2.3.2 in /tests/apps/python-flask +- #5839: @dependabot[bot] chore(deps): bump mkdocs from 1.4.2 to 1.4.3 in /docs/_build +- #5835: @dependabot[bot] chore(deps): bump golang from 1.20.3 to 1.20.4 in /tests/apps/zombies-dockerfile-no-tini +- #5838: @dependabot[bot] chore(deps): bump golang from 1.20.3 to 1.20.4 in /tests/apps/go-fail-predeploy +- #5841: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.9.0 to 0.10.0 in /tests/apps/gogrpc +- #5842: @dependabot[bot] chore(deps): bump werkzeug from 2.3.1 to 2.3.4 in /tests/apps/python-flask +- #5844: @dependabot[bot] chore(deps): bump django from 4.1.7 to 4.1.9 in /tests/apps/dockerfile-release +- #5848: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.8 to 9.1.12 in /docs/_build +- #5840: @dependabot[bot] chore(deps): bump golang from 1.20.3 to 1.20.4 in /tests/apps/go-fail-postdeploy +- #5823: @dependabot[bot] chore(deps): bump flask from 2.2.3 to 2.3.1 in /tests/apps/python-flask +- #5825: @dependabot[bot] chore(deps): bump flask from 2.2.3 to 2.3.1 in /tests/apps/multi +- #5826: @dependabot[bot] chore(deps): bump werkzeug from 2.2.3 to 2.3.1 in /tests/apps/python-flask + +## 0.30.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.3/bootstrap.sh +sudo DOKKU_TAG=v0.30.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #5763: @josegonzalez Set dokku version correctly for digitalocean image build + +### New Features + +- #5799: @josegonzalez Add ability to run dokku report without downloading herokuish image + +### Documentation + +- #5810: @strugee Fix typo in 0.30.x migration guide +- #5820: @tylercrumpton Fix "cotainer" typo in one-off tasks docs page. +- #5800: @josegonzalez Fix indentation in registry configuration documentation + +### Dependencies + +- #5809: @dependabot[bot] chore(deps): bump pygments from 2.15.0 to 2.15.1 in /docs/_build +- #5811: @dependabot[bot] chore(deps): bump node from 19-alpine to 20-alpine in /tests/apps/dockerfile-noexpose +- #5812: @dependabot[bot] chore(deps): bump node from 19-alpine to 20-alpine in /tests/apps/dockerfile-procfile +- #5813: @dependabot[bot] chore(deps): bump node from 19-alpine to 20-alpine in /tests/apps/dockerfile-procfile-bad +- #5814: @dependabot[bot] chore(deps): bump node from 19-alpine to 20-alpine in /tests/apps/dockerfile +- #5815: @dependabot[bot] chore(deps): bump node from 19-alpine to 20-alpine in /tests/apps/dockerfile-app-json-formations +- #5816: @dependabot[bot] chore(deps): bump sqlparse from 0.4.3 to 0.4.4 in /tests/apps/dockerfile-release +- #5818: @dependabot[bot] chore(deps): bump importlib-metadata from 6.5.0 to 6.6.0 in /docs/_build +- #5821: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.6 to 9.1.8 in /docs/_build +- #5805: @dependabot[bot] chore(deps): bump jetty-servlet from 11.0.14 to 11.0.15 in /tests/apps/java +- #5808: @dependabot[bot] chore(deps): bump importlib-metadata from 6.3.0 to 6.5.0 in /docs/_build +- #5802: @dependabot[bot] chore(deps): bump packaging from 23.0 to 23.1 in /docs/_build +- #5806: @dependabot[bot] chore(deps): bump soupsieve from 2.4 to 2.4.1 in /docs/_build +- #5779: @dependabot[bot] chore(deps): bump golang from 1.20.2 to 1.20.3 in /tests/apps/gogrpc +- #5782: @dependabot[bot] chore(deps): bump golang from 1.20.2 to 1.20.3 in /tests/apps/go-fail-postdeploy +- #5788: @dependabot[bot] chore(deps): bump python from 3.11.2-alpine to 3.11.3-alpine in /docs/_build +- #5795: @dependabot[bot] chore(deps): bump pygments from 2.14.0 to 2.15.0 in /docs/_build +- #5796: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.12.0 to 4.12.2 in /docs/_build +- #5797: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.4 to 9.1.6 in /docs/_build +- #5801: @dependabot[bot] chore(deps): bump sinatra from 3.0.5 to 3.0.6 in /tests/apps/ruby +- #5794: @dependabot[bot] chore(deps): bump pymdown-extensions from 9.10 to 9.11 in /docs/_build +- #5793: @dependabot[bot] chore(deps): bump importlib-metadata from 6.1.0 to 6.3.0 in /docs/_build +- #5791: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.8.0 to 0.9.0 in /tests/apps/gogrpc +- #5789: @dependabot[bot] chore(deps): bump python from 3.11.2-buster to 3.11.3-buster in /tests/apps/dockerfile-release +- #5783: @dependabot[bot] chore(deps): bump golang from 1.20.2 to 1.20.3 in /tests/apps/zombies-dockerfile-tini +- #5781: @dependabot[bot] chore(deps): bump golang from 1.20.2 to 1.20.3 in /tests/apps/zombies-dockerfile-no-tini +- #5780: @dependabot[bot] chore(deps): bump golang from 1.20.2 to 1.20.3 in /tests/apps/go-fail-predeploy +- #5775: @dependabot[bot] chore(deps): bump ruby from 3.2.1 to 3.2.2 in /tests/apps/dockerfile-entrypoint +- #5774: @dependabot[bot] chore(deps): bump thin from 1.8.1 to 1.8.2 in /tests/apps/ruby +- #5765: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.3 to 9.1.4 in /docs/_build + +## 0.30.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.2/bootstrap.sh +sudo DOKKU_TAG=v0.30.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #5749: @josegonzalez Keep image specified by git:from-image on app rebuild +- #5750: @josegonzalez Return an error when an invalid scale count is encountered +- #5748: @josegonzalez Correct set source type and source metadata + +### New Features + +- #5754: @josegonzalez Ignore release branches in changelog output + +### Removals + +- #5756: @josegonzalez Drop code supporting git versions without worktree support + +### Documentation + +- #5744: @josegonzalez Add DOKKU_LIB_HOST_ROOT to docker usage docs +- #5740: @josegonzalez Add a note about how running plugin:install does not apply for docker-based installs +- #5719: @TkTech Give a quick example of what to do after disabling the proxy to expose a UDP port +- #5737: @Samuelodan Update instructions for setting letsencrypt global email +- #5736: @josegonzalez Remove extra backtick from nginx docs +- #5725: @IgnisDa Add surrealdb community plugin + +### Tests + +- #5755: @josegonzalez Ignore issue where errors are required to be lowercased + +### Dependencies + +- #5762: @dependabot[bot] chore(deps): bump watchdog from 2.3.1 to 3.0.0 in /docs/_build +- #5760: @dependabot[bot] chore(deps): bump importlib-metadata from 6.0.0 to 6.1.0 in /docs/_build +- #5761: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.11.2 to 4.12.0 in /docs/_build +- #5745: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.2 to 9.1.3 in /docs/_build +- #5746: @dependabot[bot] chore(deps): bump rack from 2.2.6.3 to 2.2.6.4 in /tests/apps/ruby +- #5732: @dependabot[bot] chore(deps): bump github.com/golang/protobuf from 1.5.2 to 1.5.3 in /tests/apps/gogrpc +- #5727: @dependabot[bot] chore(deps): bump golang from 1.20.1 to 1.20.2 in /tests/apps/zombies-dockerfile-tini +- #5708: @dependabot[bot] chore(deps): bump zipp from 3.14.0 to 3.15.0 in /docs/_build +- #5734: @dependabot[bot] chore(deps): bump mkdocs-material from 9.1.0 to 9.1.2 in /docs/_build +- #5731: @dependabot[bot] chore(deps): bump rack from 2.2.6.2 to 2.2.6.3 in /tests/apps/ruby +- #5730: @dependabot[bot] chore(deps): bump golang from 1.20.1 to 1.20.2 in /tests/apps/go-fail-predeploy +- #5729: @dependabot[bot] chore(deps): bump golang from 1.20.1 to 1.20.2 in /tests/apps/zombies-dockerfile-no-tini +- #5728: @dependabot[bot] chore(deps): bump golang from 1.20.1 to 1.20.2 in /tests/apps/gogrpc +- #5726: @dependabot[bot] chore(deps): bump golang from 1.20.1 to 1.20.2 in /tests/apps/go-fail-postdeploy +- #5723: @dependabot[bot] chore(deps): bump pymdown-extensions from 9.9.2 to 9.10 in /docs/_build +- #5722: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.7.0 to 0.8.0 in /tests/apps/gogrpc +- #5721: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.0.0-20220225172249-27dd8689420f to 0.7.0 in /plugins/common +- #5715: @dependabot[bot] chore(deps): bump luizm/action-sh-checker from 0.5.0 to 0.7.0 +- #5712: @dependabot[bot] chore(deps): bump jetty-servlet from 11.0.13 to 11.0.14 in /tests/apps/java +- #5713: @dependabot[bot] chore(deps): bump watchdog from 2.3.0 to 2.3.1 in /docs/_build +- #5717: @dependabot[bot] chore(deps): bump mkdocs-material from 9.0.14 to 9.1.0 in /docs/_build +- #5703: @dependabot[bot] chore(deps): bump golang.org/x/net from 0.0.0-20190813141303-74dc4d7220e7 to 0.7.0 in /tests/apps/gogrpc +- #5705: @dependabot[bot] chore(deps): bump mkdocs-material from 9.0.13 to 9.0.14 in /docs/_build +- #5704: @dependabot[bot] chore(deps): bump watchdog from 2.2.1 to 2.3.0 in /docs/_build +- #5698: @dependabot[bot] chore(deps): bump golang.org/x/text from 0.3.7 to 0.3.8 in /plugins/common +- #5697: @dependabot[bot] chore(deps): bump golang.org/x/text from 0.3.2 to 0.3.8 in /tests/apps/gogrpc + +### Other + +- #5751: @josegonzalez Update all golang dependencies to fix go.0 tests + +## 0.30.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.1/bootstrap.sh +sudo DOKKU_TAG=v0.30.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #5690: @josegonzalez Update dependencies for golang code to fix builds +- #5683: @josegonzalez Ensure the services directory also exists +- #5688: @josegonzalez Set custom entrypoint for pack-based containers so custom command/argument combinations are respected + +### New Features + +- #5682: @josegonzalez Build Digitalocean image automatically upon release +- #5675: @josegonzalez Add new sections to release notes + +### Documentation + +- #5695: @josegonzalez Add a note about required upgrade to 0.29.x for versions prior to 0.25.x +- #5689: @josegonzalez Add note about ubuntu 18.04 being deprecated +- #5684: @josegonzalez Note that the app.json is pulled from the specified image when deploying via git:from-image +- #5679: @josegonzalez Revert invalid replacement in homepage html path objects +- #5678: @josegonzalez Cleanup docs on homepage for installation + +### Dependencies + +- #5693: @dependabot[bot] chore(deps): bump mkdocs-material from 9.0.12 to 9.0.13 in /docs/_build +- #5694: @dependabot[bot] chore(deps): bump zipp from 3.13.0 to 3.14.0 in /docs/_build +- #5677: @dependabot[bot] chore(deps): bump flask from 2.2.2 to 2.2.3 in /tests/apps/multi +- #5676: @dependabot[bot] chore(deps): bump flask from 2.2.2 to 2.2.3 in /tests/apps/python-flask + +## 0.30.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.30.0/bootstrap.sh +sudo DOKKU_TAG=v0.30.0 bash bootstrap.sh +``` + +See the [0.30.0 migration guide](/docs/appendices/0.30.0-migration-guide.md) for more information on migrating to 0.30.0. + +### Bug Fixes + +- #5674: @josegonzalez Use authenticated requests to the github api in order to generate changelogs for a release +- #5604: @josegonzalez Trigger nginx proxy rebuild early to avoid downtime if deploying large numbers of processes +- #5589: @josegonzalez Use warning_scheme variable instead of bare word in caddy-vhosts warning output +- #5580: @josegonzalez Do not attempt to reinstall already installed plugins + +### New Features + +- #5663: @josegonzalez Update build-base docker image to ensure we can build on all supported platforms without stalling +- #5308: @josegonzalez Implement haproxy proxy plugin +- #5633: @josegonzalez Add auto-updates to docs python dependencies +- #5603: @josegonzalez Add ability to deploy images generated via docker save with git:load-image +- #5591: @josegonzalez Allow the herokuish builder to be detected when the computed allowed value is set not set to false +- #5570: @josegonzalez Add support for customizing the remote in use for all commands sent via the official client +- #5581: @josegonzalez Use short url for installation + +### Refactors + +- #5659: @josegonzalez Remove deprecated functions and commands + +### Documentation + +- #5658: @mpslanker Updated debian install docs to work on both debian and ubuntu +- #5657: @josegonzalez Use correct version of Markdown for mkdocs +- #5656: @josegonzalez Add "new as of" prefix to version in user management docs +- #5645: @josegonzalez Update mkdocs-material features to cope with upgrade to 9.x +- #5632: @josegonzalez Add a note mentioning that network aliases only work when attached to a network other than bridge +- #5630: @josegonzalez Remove extra location block from built docs nginx config +- #5616: @jcalfee Add a note about reloading nginx to troubleshooting docs +- #5590: @josegonzalez Note that connecting to vagrant should be done via vagrant ssh +- #5588: @josegonzalez Document how to route to server-local apps via a service proxy +- #5585: @NicolasLM Improve instructions for disabling the default nginx site + +### Tests + +- #5673: @josegonzalez Cancel in progress builds when a new commit is pushed +- #5611: @josegonzalez Add more debugging output to deploy tests +- #5587: @josegonzalez Timeout build jobs within 45 minutes + +### Other + +- #5671: @dependabot[bot] chore(deps): bump soupsieve from 2.3.2.post1 to 2.4 in /docs/_build +- #5666: @dependabot[bot] chore(deps): bump golang from 1.20.0 to 1.20.1 in /tests/apps/go-fail-predeploy +- #5672: @dependabot[bot] chore(deps): bump django from 4.1.6 to 4.1.7 in /tests/apps/dockerfile-release +- #5670: @dependabot[bot] chore(deps): bump golang from 1.20.0 to 1.20.1 in /tests/apps/zombies-dockerfile-no-tini +- #5669: @dependabot[bot] chore(deps): bump golang from 1.20.0 to 1.20.1 in /tests/apps/go-fail-postdeploy +- #5668: @dependabot[bot] chore(deps): bump golang from 1.20.0 to 1.20.1 in /tests/apps/gogrpc +- #5667: @dependabot[bot] chore(deps): bump golang from 1.20.0 to 1.20.1 in /tests/apps/zombies-dockerfile-tini +- #5665: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 230 to 231 in /tests/apps/php +- #5664: @dependabot[bot] chore(deps): bump werkzeug from 2.2.2 to 2.2.3 in /tests/apps/python-flask +- #5559: @josegonzalez Move app.json extraction from built image artifact to source code +- #5661: @dependabot[bot] chore(deps): update markdown requirement from <3.4,>=3.2.1 to >=3.2.1,<3.5 in /docs/_build +- #5655: @dependabot[bot] chore(deps): bump zipp from 3.12.1 to 3.13.0 in /docs/_build +- #5654: @dependabot[bot] chore(deps): bump mkdocs-material from 9.0.11 to 9.0.12 in /docs/_build +- #5648: @dependabot[bot] chore(deps): bump markdown from 3.3.7 to 3.4.1 in /docs/_build +- #5650: @dependabot[bot] chore(deps): bump python from 3.11.1-buster to 3.11.2-buster in /tests/apps/dockerfile-release +- #5651: @dependabot[bot] chore(deps): bump ruby from 3.2.0 to 3.2.1 in /tests/apps/dockerfile-entrypoint +- #5652: @dependabot[bot] chore(deps): bump python from 3.11.1-alpine to 3.11.2-alpine in /docs/_build +- #5647: @dependabot[bot] chore(deps): bump importlib-metadata from 4.12.0 to 6.0.0 in /docs/_build +- #5646: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 229 to 230 in /tests/apps/php +- #5634: @dependabot[bot] chore(deps): bump mkdocs-material from 8.4.2 to 9.0.11 in /docs/_build +- #5637: @dependabot[bot] chore(deps): bump packaging from 21.3 to 23.0 in /docs/_build +- #5643: @dependabot[bot] chore(deps): bump watchdog from 2.1.9 to 2.2.1 in /docs/_build +- #5642: @dependabot[bot] chore(deps): bump pymdown-extensions from 9.5 to 9.9.2 in /docs/_build +- #5639: @dependabot[bot] chore(deps): bump mkdocs from 1.3.1 to 1.4.2 in /docs/_build +- #5638: @dependabot[bot] chore(deps): bump mkdocs-material-extensions from 1.0.3 to 1.1.1 in /docs/_build +- #5631: @dependabot[bot] chore(deps): bump socket.io from 4.5.4 to 4.6.0 in /tests/apps/.websocket.disabled +- #5636: @dependabot[bot] chore(deps): bump markupsafe from 2.1.1 to 2.1.2 in /docs/_build +- #5641: @dependabot[bot] chore(deps): bump beautifulsoup4 from 4.11.1 to 4.11.2 in /docs/_build +- #5635: @dependabot[bot] chore(deps): bump pygments from 2.13.0 to 2.14.0 in /docs/_build +- #5640: @dependabot[bot] chore(deps): bump zipp from 3.8.1 to 3.12.1 in /docs/_build +- #5629: @josegonzalez Update list indentation explaining network property utilization +- #5617: @dependabot[bot] chore(deps): bump golang from 1.19.5 to 1.20.0 in /tests/apps/go-fail-predeploy +- #5620: @dependabot[bot] chore(deps): bump golang from 1.19.5 to 1.20.0 in /tests/apps/gogrpc +- #5618: @dependabot[bot] chore(deps): bump golang from 1.19.5 to 1.20.0 in /tests/apps/go-fail-postdeploy +- #5625: @dependabot[bot] chore(deps): bump django from 4.1.2 to 4.1.6 in /tests/apps/dockerfile-release +- #5619: @dependabot[bot] chore(deps): bump golang from 1.19.5 to 1.20.0 in /tests/apps/zombies-dockerfile-no-tini +- #5621: @dependabot[bot] chore(deps): bump golang from 1.19.5 to 1.20.0 in /tests/apps/zombies-dockerfile-tini +- #5615: @dependabot[bot] chore(deps): bump ludeeus/action-shellcheck from 1.1.0 to 2.0.0 +- #5610: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 227 to 229 in /tests/apps/php +- #5608: @josegonzalez tests: refactor the config test to use a simple python codebase instead +- #5606: @josegonzalez Ensure the mounted services directory is symlinked correctly +- #5558: @josegonzalez Remove references to ubuntu 18.04 ahead of EOL +- #5600: @dependabot[bot] chore(deps): bump rack from 2.2.4 to 2.2.6.2 in /tests/apps/ruby +- #5598: @dependabot[bot] chore(deps): bump hadolint/hadolint-action from 3.0.0 to 3.1.0 +- #5595: @josegonzalez Remove deprecated ability to call logs:failed without specifying an app or --all flag +- #5594: @josegonzalez Remove deprecated builder-specific post-release hooks +- #5593: @josegonzalez Remove deprecated --detach flag +- #5592: @josegonzalez Remove support for DOKKU_SCALE file +- #5560: @josegonzalez Drop SPDY support from nginx plugin +- #5579: @dependabot[bot] chore(deps): bump maven-dependency-plugin from 3.4.0 to 3.5.0 in /tests/apps/java + +## 0.29.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.29.4/bootstrap.sh +sudo DOKKU_TAG=v0.29.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #5571: @josegonzalez Ensure we properly setup all data directories on app install and creation +- #5555: @josegonzalez Drop debugging code from ps plugin + +### Documentation + +- #5563: @kbuilds Clarify documentation for default site under nginx + +### Tests + +- #5557: @josegonzalez Update GitHub actions + +### Other + +- #5577: @dependabot[bot] chore(deps): bump golang from 1.19.4 to 1.19.5 in /tests/apps/gogrpc +- #5573: @dependabot[bot] chore(deps): bump golang from 1.19.4 to 1.19.5 in /tests/apps/go-fail-postdeploy +- #5574: @dependabot[bot] chore(deps): bump golang from 1.19.4 to 1.19.5 in /tests/apps/zombies-dockerfile-tini +- #5575: @dependabot[bot] chore(deps): bump golang from 1.19.4 to 1.19.5 in /tests/apps/go-fail-predeploy +- #5576: @dependabot[bot] chore(deps): bump golang from 1.19.4 to 1.19.5 in /tests/apps/zombies-dockerfile-no-tini + +## 0.29.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.29.3/bootstrap.sh +sudo DOKKU_TAG=v0.29.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #5544: @josegonzalez Exit non-zero when git:from-image deploys fail to start the app +- #5543: @josegonzalez Properly parse arguments for git:status command + +### Tests + +- #5554: @josegonzalez Validate that procfile-path is respected + +### Other + +- #5551: @dependabot[bot] chore(deps): bump ruby from 3.1.3 to 3.2.0 in /tests/apps/dockerfile-entrypoint +- #5547: @dependabot[bot] chore(deps): bump setuptools from 65.5.0 to 65.5.1 in /tests/apps/dockerfile-release +- #5542: @josegonzalez Add json output format support to storage:list + +## 0.29.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.29.2/bootstrap.sh +sudo DOKKU_TAG=v0.29.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #5537: @jamesremuscat Correctly include `auth` middleware on `api` router. + +## 0.29.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.29.1/bootstrap.sh +sudo DOKKU_TAG=v0.29.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #5532: @josegonzalez Fix issue where git:from-image deploys may result in extracted files not being respected + +### Documentation + +- #5531: @josegonzalez Fix indentation on heading in where to get help +- #5521: @AlejandroAkbal Remove reference to `--rm` flag cron task documentation +- #5517: @josegonzalez Fix reference to scheduler name in kubernetes docs +- #5516: @josegonzalez Fix reference to scheduler name in nomad docs + +### Tests + +- #5533: @josegonzalez Add a test case for nested vector-sink values with plus signs + +### Other + +- #5534: @dependabot[bot] chore(deps): bump sinatra from 3.0.4 to 3.0.5 in /tests/apps/ruby +- #5514: @dependabot[bot] chore(deps): bump golang from 1.19.3 to 1.19.4 in /tests/apps/go-fail-predeploy +- #5512: @dependabot[bot] chore(deps): bump golang from 1.19.3 to 1.19.4 in /tests/apps/go-fail-postdeploy +- #5513: @dependabot[bot] chore(deps): bump golang from 1.19.3 to 1.19.4 in /tests/apps/zombies-dockerfile-no-tini +- #5511: @dependabot[bot] chore(deps): bump golang from 1.19.3 to 1.19.4 in /tests/apps/zombies-dockerfile-tini +- #5510: @dependabot[bot] chore(deps): bump golang from 1.19.3 to 1.19.4 in /tests/apps/gogrpc +- #5525: @dependabot[bot] chore(deps): bump jetty-servlet from 11.0.12 to 11.0.13 in /tests/apps/java +- #5519: @dependabot[bot] chore(deps): bump python from 3.11.0-buster to 3.11.1-buster in /tests/apps/dockerfile-release +- #5520: @dependabot[bot] chore(deps): bump python from 3.11.0-alpine to 3.11.1-alpine in /docs/_build + +## 0.29.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.29.0/bootstrap.sh +sudo DOKKU_TAG=v0.29.0 bash bootstrap.sh +``` + +See the [0.29.0 migration guide](/docs/appendices/0.29.0-migration-guide.md) for more information on migrating to 0.29.0. + +### Bug Fixes + +- #5506: @josegonzalez Add support for escaped plus (+) signs in vector configurations +- #5505: @josegonzalez Add missing event hooks +- #5493: @josegonzalez Correct issue with detection of non-https port mapping as https port mapping +- #5499: @josegonzalez Do not use traefik priority in service name +- #5491: @josegonzalez Add missing http part to traefik routing rule +- #5477: @josegonzalez Add missing config_sub entry to config gitignore +- #5474: @josegonzalez Add missing source call +- #5345: @esirK Always set app created-at via property instead of introspecting on folder creation time +- #5456: @cooperaj Ensure Traefik only attempts to obtain certificate for api when enabled +- #5466: @josegonzalez Drop the app argument when calling storage:ensure-directory from remote client +- #5458: @cooperaj Ensure the TLS resolver is specifed for HTTPS enabled apps +- #5448: @josegonzalez Do not attempt to attach a network to a container that already has it + +### New Features + +- #5494: @josegonzalez Silence the warning message when there are no dokku apps on install +- #5490: @josegonzalez Add ability to set a custom priority for traefik routing rules +- #5489: @josegonzalez Use exit code 137 to signify a lock was unable to be retrieved when calling ps:retire +- #5487: @josegonzalez Use a priority when routing requests with traefik +- #5481: @josegonzalez Drop installation of nginx ppa +- #5478: @josegonzalez Allow users to enable herokuish usage on arm/arm64 platforms +- #5479: @josegonzalez Cleanup builder code and docs +- #5476: @josegonzalez Add fileutils plugin to the devcontainer +- #5473: @josegonzalez Add warning when a user is attempting to deploy with an ipv4/ipv6 domain name and has the default nginx site file available +- #5451: @josegonzalez Add git:status subcommand +- #5349: @josegonzalez Error out when executing caddy and traefik commands without the compose plugin installed +- #5322: @josegonzalez Implement run:logs command +- #5321: @josegonzalez Implement run:stop command +- #5434: @josegonzalez Upgrade to go 1.19 everywhere +- #5320: @josegonzalez Add json format output to run:list + +### Refactors + +- #5488: @josegonzalez De-duplicate proxy building and simplify url generation +- #5454: @josegonzalez Switch from base64-encoding to base36-encoding of cron task IDs + +### Documentation + +- #5509: @josegonzalez Update data loss blockquote to be warning +- #5507: @josegonzalez Add tutorials link to navigation +- #5503: @josegonzalez Document potential deployment downtime +- #5484: @IgnisDa Add edgedb community plugin +- #5482: @JonathanMH Fixes minor typos in proxy docs +- #5465: @josegonzalez Fix embedded lists on deployment tasks +- #5430: @pablobm Clarify that proxy:build-config is required for any nginx:set call +- #5447: @josegonzalez Specify correct command for builder-lambda:report +- #5429: @pablobm Clarify details of the default template + +### Tests + +- #5449: @josegonzalez Install goverage via go get + +### Other + +- #5500: @dependabot[bot] chore(deps): bump maven-dependency-plugin from 3.3.0 to 3.4.0 in /tests/apps/java +- #5472: @josegonzalez Start started proxies automatically during ps:restore +- #5497: @dependabot[bot] chore(deps): bump ruby from 3.1.2 to 3.1.3 in /tests/apps/dockerfile-entrypoint +- #5496: @dependabot[bot] chore(deps): bump sinatra from 3.0.3 to 3.0.4 in /tests/apps/ruby +- #5480: @josegonzalez Cleanup zero'd out processes when a Procfile omitting those process types is set +- #5475: @josegonzalez Refactor nginx.conf.sigil to be extracted from a specified path in source code instead of the built image +- #5467: @josegonzalez Move DOKKU_WAIT_TO_RETIRE to a checks property +- #5455: @dependabot[bot] chore(deps): bump hadolint/hadolint-action from 2.1.0 to 3.0.0 +- #5463: @dependabot[bot] chore(deps): bump sinatra from 3.0.2 to 3.0.3 in /tests/apps/ruby +- #5450: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 226 to 227 in /tests/apps/php +- #5437: @dependabot[bot] chore(deps): bump golang from 1.19.2 to 1.19.3 in /tests/apps/zombies-dockerfile-tini +- #5441: @dependabot[bot] chore(deps): bump golang from 1.19.2 to 1.19.3 in /tests/apps/gogrpc +- #5440: @dependabot[bot] chore(deps): bump golang from 1.19.2 to 1.19.3 in /tests/apps/go-fail-predeploy +- #5439: @dependabot[bot] chore(deps): bump golang from 1.19.2 to 1.19.3 in /tests/apps/zombies-dockerfile-no-tini +- #5438: @dependabot[bot] chore(deps): bump golang from 1.19.2 to 1.19.3 in /tests/apps/go-fail-postdeploy +- #5431: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 225 to 226 in /tests/apps/php +- #5426: @dependabot[bot] chore(deps): bump python from 3.10.0-buster to 3.11.0-buster in /tests/apps/dockerfile-release +- #5319: @josegonzalez Use container name instead of container id for run:detached output +- #5427: @dependabot[bot] chore(deps): bump python from 3.10.8-alpine to 3.11.0-alpine in /docs/_build + +## 0.28.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.28.4/bootstrap.sh +sudo DOKKU_TAG=v0.28.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #5423: @josegonzalez Respect PLUGIN_PATH when checking if a plugin trigger exists +- #5425: @josegonzalez Ignore https mappings when no ssl certificate exists + +### New Features + +- #5424: @josegonzalez Download all src files for go mod dependencies for use in vscode + +### Documentation + +- #5421: @josegonzalez Document that scaling via app.json will set all other processes to zero + +### Tests + +- #5422: @josegonzalez Test that build-arg works as expected + +### Other + +- #5418: @dependabot[bot] chore(deps): bump node from 18-alpine to 19-alpine in /tests/apps/dockerfile-noexpose +- #5417: @dependabot[bot] chore(deps): bump node from 18-alpine to 19-alpine in /tests/apps/dockerfile-procfile-bad +- #5419: @dependabot[bot] chore(deps): bump node from 18-alpine to 19-alpine in /tests/apps/dockerfile-dokku-scale +- #5416: @dependabot[bot] chore(deps): bump node from 18-alpine to 19-alpine in /tests/apps/dockerfile-procfile +- #5415: @dependabot[bot] chore(deps): bump node from 18-alpine to 19-alpine in /tests/apps/dockerfile +- #5413: @dependabot[bot] chore(deps): bump ibiqlik/action-yamllint from 3.1.0 to 3.1.1 + +## 0.28.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.28.3/bootstrap.sh +sudo DOKKU_TAG=v0.28.3 bash bootstrap.sh +``` + +### New Features + +- #5394: @josegonzalez Add support for automatically updating Dockerfile FROM statements via dependabot + +### Other + +- #5411: @dependabot[bot] chore(deps): bump python from 3.8-buster to 3.10.0-buster in /tests/apps/dockerfile-release +- #5400: @dependabot[bot] chore(deps): bump node from 4-alpine to 18-alpine in /tests/apps/dockerfile-procfile +- #5405: @dependabot[bot] chore(deps): bump node from 4-alpine to 18-alpine in /tests/apps/dockerfile +- #5397: @dependabot[bot] chore(deps): bump ruby from 2.5.1 to 3.1.2 in /tests/apps/dockerfile-entrypoint +- #5401: @dependabot[bot] chore(deps): bump golang from 1.17.1 to 1.19.2 in /tests/apps/go-fail-postdeploy +- #5403: @dependabot[bot] chore(deps): bump golang from 1.17.1 to 1.19.2 in /tests/apps/zombies-dockerfile-tini +- #5407: @dependabot[bot] chore(deps): bump python from 3.10-alpine to 3.10.8-alpine in /docs/_build +- #5404: @dependabot[bot] chore(deps): bump node from 4-alpine to 18-alpine in /tests/apps/dockerfile-dokku-scale +- #5402: @dependabot[bot] chore(deps): bump node from 4-alpine to 18-alpine in /tests/apps/dockerfile-procfile-bad +- #5398: @dependabot[bot] chore(deps): bump golang from 1.17.1 to 1.19.2 in /tests/apps/go-fail-predeploy +- #5396: @dependabot[bot] chore(deps): bump golang from 1.17.1 to 1.19.2 in /tests/apps/gogrpc +- #5399: @dependabot[bot] chore(deps): bump golang from 1.17.1 to 1.19.2 in /tests/apps/zombies-dockerfile-no-tini +- #5395: @dependabot[bot] chore(deps): bump node from 4-alpine to 18-alpine in /tests/apps/dockerfile-noexpose + +## 0.28.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.28.2/bootstrap.sh +sudo DOKKU_TAG=v0.28.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #5393: @josegonzalez Correct issue with replacing the version in the installation guide +- #5354: @josegonzalez Report the correct value for the letsencrypt server +- #5347: @josegonzalez Set correct version on builder-lambda plugin + +### New Features + +- #5392: @josegonzalez Add support for devcontainers on ARM64 instances + +### Documentation + +- #5365: @josegonzalez Use correct indentation for embedded list in remote commands documentation +- #5357: @josegonzalez Reference blog on main domain +- #5355: @josegonzalez Autogenerate documentation in CI +- #5351: @josegonzalez Fix formatting on list in deployment tasks +- #5328: @holtergram Call out git remote change when renaming an app +- #5344: @josegonzalez Generate docs via mkdocs +- #5342: @josegonzalez Ensure doc links work on github as well as on docs site +- #5336: @josegonzalez Add note about docker-compose requirement for using the caddy plugin +- #5337: @josegonzalez Add note about docker-compose requirement for using the traefikâ€Ļ +- #5332: @cdubz Remove errant semicolon from wget command +- #5329: @josegonzalez Use updated url for gliderlabs slack invite app +- #5315: @alexislefebvre Fix doc link for systems with less than 1GB memory + +### Other + +- #5382: @dependabot[bot] chore(deps): bump sinatra from 2.2.2 to 3.0.2 in /tests/apps/ruby +- #5386: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 224 to 225 in /tests/apps/php +- #5361: @dependabot[bot] chore(deps): bump jetty-servlet from 11.0.11 to 11.0.12 in /tests/apps/java +- #5346: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 223 to 224 in /tests/apps/php + +## 0.28.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.28.1/bootstrap.sh +sudo DOKKU_TAG=v0.28.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #5313: @josegonzalez Fix issues with multiple domains in new proxy plugins + +## 0.28.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.28.0/bootstrap.sh +sudo DOKKU_TAG=v0.28.0 bash bootstrap.sh +``` + +See the [0.28.0 migration guide](/docs/appendices/0.28.0-migration-guide.md) for more information on migrating to 0.28.0. + +### Bug Fixes + +- #5299: @josegonzalez Do not trigger restarts when mutating config globally +- #5298: @josegonzalez Set core.bare=true on correct repository path + +### New Features + +- #5300: @josegonzalez Add the ability to disable the init process +- #5295: @josegonzalez Add an official caddy proxy implementation +- #5293: @josegonzalez Add an official traefik proxy implementation +- #5202: @josegonzalez Add builder-lambda plugin + +### Documentation + +- #5296: @janlavicka Fix typo in kubernetes docs +- #5288: @josegonzalez Do not have bottom border float on page +- #5283: @alexgleason Homepage: fix link in quick install guide + +### Other + +- #5297: @josegonzalez Update vector image +- #5294: @josegonzalez Document and enable alternative proxy implementations +- #5291: @dependabot[bot] chore(deps): bump werkzeug from 2.2.1 to 2.2.2 in /tests/apps/python-flask +- #5292: @dependabot[bot] chore(deps): bump flask from 2.2.1 to 2.2.2 in /tests/apps/multi +- #5290: @dependabot[bot] chore(deps): bump flask from 2.2.1 to 2.2.2 in /tests/apps/python-flask +- #5227: @josegonzalez Drop forwarded port from vagrant setup +- #5285: @josegonzalez Update all development dependencies +- #5240: @josegonzalez Drop support for CentOS, Fedora, OpenSuse, as well as Debian 9 +- #5281: @dependabot[bot] chore(deps): bump flask from 2.1.3 to 2.2.1 in /tests/apps/multi +- #5280: @dependabot[bot] chore(deps): bump flask from 2.1.3 to 2.2.1 in /tests/apps/python-flask +- #5284: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 222 to 223 in /tests/apps/php + +## 0.27.10 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.10/bootstrap.sh +sudo DOKKU_TAG=v0.27.10 bash bootstrap.sh +``` + +### Bug Fixes + +- #5276: @d6rkaiz Remove duplicate syntax for nginx.conf + +### Other + +- #5274: @kennell readme: mention Ubuntu 22.04 compability + +## 0.27.9 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.9/bootstrap.sh +sudo DOKKU_TAG=v0.27.9 bash bootstrap.sh +``` + +### Bug Fixes + +- #5273: @josegonzalez Correct issue where more complex docker args were not properly parsed when scheduling containers +- #5272: @josegonzalez Add support for --global as app name when calling config-get trigger +- #5263: @alexgleason Update deprecated apt-key command + +### New Features + +- #5262: @josegonzalez Run shfmt against all test files +- #5257: @chrisjdixon Move nginx.conf.d includes after all other hard-coded config to allow for overrides + +### Other + +- #5184: @yasoob New website. Closes #5175 +- #5270: @dependabot[bot] chore(deps): bump werkzeug from 2.2.0 to 2.2.1 in /tests/apps/python-flask +- #5265: @dependabot[bot] chore(deps): bump werkzeug from 2.1.2 to 2.2.0 in /tests/apps/python-flask +- #5264: @dependabot[bot] chore(deps): bump sinatra from 2.2.1 to 2.2.2 in /tests/apps/ruby +- #5261: @dependabot[bot] chore(deps): bump luizm/action-sh-checker from 0.4.0 to 0.5.0 +- #5258: @dependabot[bot] chore(deps): bump sinatra from 2.2.0 to 2.2.1 in /tests/apps/ruby + +## 0.27.8 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.8/bootstrap.sh +sudo DOKKU_TAG=v0.27.8 bash bootstrap.sh +``` + +### Bug Fixes + +- #5232: @YuukiHogo builder-build fix for DOKKU_DOCKERFILE_CACHE_BUILD and DOKKU_DOCKER_BUILD_OPTS variables + +### New Features + +- #5255: @josegonzalez export DOKKU_COMMAND for use in authentication systems + +### Other + +- #5250: @dependabot[bot] chore(deps): bump flask from 2.1.2 to 2.1.3 in /tests/apps/multi +- #5251: @dependabot[bot] chore(deps): bump flask from 2.1.2 to 2.1.3 in /tests/apps/python-flask +- #5245: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 221 to 222 in /tests/apps/php + +## 0.27.7 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.7/bootstrap.sh +sudo DOKKU_TAG=v0.27.7 bash bootstrap.sh +``` + +### Bug Fixes + +- #5238: @josegonzalez Respect dokku system user/group when setting storage permissions on installation + +### New Features + +- #5239: @josegonzalez Add builder-type and image-stage labels to all images + +### Documentation + +- #5241: @josegonzalez Clarify where logs are pulled from and general log shipping stance +- #5237: @josegonzalez Fix builder-pack:set command example + +### Other + +- #5235: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 220 to 221 in /tests/apps/php + +## 0.27.6 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.6/bootstrap.sh +sudo DOKKU_TAG=v0.27.6 bash bootstrap.sh +``` + +### Bug Fixes + +- #5225: @josegonzalez Use original app name when setting include_labels for vector source +- #5228: @josegonzalez Always respect app.json formations in scale settings +- #5201: @josegonzalez Use pack as default builder for arm64 servers + +### Documentation + +- #5230: @josegonzalez Clarify usage of build args to set environment variables for Dockerfile deploys +- #5208: @asokoloski-m Fix typo in dockerfile builder docs +- #5206: @AxelTheGerman Added dokku-litestream to list of community plugins + +### Other + +- #5218: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 219 to 220 in /tests/apps/php +- #5222: @dependabot[bot] chore(deps): bump jetty-servlet from 11.0.10 to 11.0.11 in /tests/apps/java +- #5224: @josegonzalez Document that the community registry plugin should be uninstalled before upgrading past 0.24.x +- #5220: @dependabot[bot] chore(deps): bump jetty-servlet from 11.0.9 to 11.0.10 in /tests/apps/java +- #5212: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 218 to 219 in /tests/apps/php +- #5210: @dependabot[bot] chore(deps): bump monolog/monolog from 1.27.0 to 1.27.1 in /tests/apps/php +- #5204: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 217 to 218 in /tests/apps/php + +## 0.27.5 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.5/bootstrap.sh +sudo DOKKU_TAG=v0.27.5 bash bootstrap.sh +``` + +### Bug Fixes + +- #5183: @josegonzalez Filter apps when verifying app names +- #5199: @josegonzalez Revert "fix: Vector fails to start container due to --watch-config flag" +- #5197: @holamendi fix: Vector fails to start container due to --watch-config flag +- #5192: @josegonzalez Confirm installation of universe packages when installing on ubuntu +- #5193: @josegonzalez Force-set correct permissions on env files +- #5181: @josegonzalez Ensure we default SSH_NAME to NAME when filtering apps + +### New Features + +- #5196: @josegonzalez Disable herokuish usage on both armhf and arm64 platforms +- #5194: @josegonzalez Upgrade base Dockerfile image to phusion/baseimage:focal-1.2.0 + +### Other + +- #5200: @dependabot[bot] chore(deps): bump rack from 2.2.3 to 2.2.3.1 in /tests/apps/ruby +- #5198: @dependabot[bot] chore(deps): bump grunt from 1.5.2 to 1.5.3 in /tests/apps/multi +- #5187: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 216 to 217 in /tests/apps/php + +## 0.27.4 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.4/bootstrap.sh +sudo DOKKU_TAG=v0.27.4 bash bootstrap.sh +``` + +### Documentation + +- #5180: @Bilal-io fixes rst-versions height + +## 0.27.3 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.3/bootstrap.sh +sudo DOKKU_TAG=v0.27.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #5178: @josegonzalez Fix app filtering for authenticated usage +- #5173: @bitjockey42 Add 22.04 to supported distro versions + +### Documentation + +- #5179: @Bilal-io Fixes to the Docs version switcher +- #5177: @josegonzalez docs: cleanup experimental warning in scheduler docs +- #5176: @josegonzalez Update installation requirements to mention arm64 and ubuntu 22.04 +- #5174: @vladdoster Fix typos and phrasing in remote commands documentation + +## 0.27.2 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.2/bootstrap.sh +sudo DOKKU_TAG=v0.27.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #5166: @josegonzalez Ensure permissions for global VHOST file are set to the dokku user on install +- #5141: @josegonzalez fix: set correct version on scheduler plugin +- #5069: @josegonzalez Do not start intermediate container when calling dokku run after a failed build + +### New Features + +- #5168: @josegonzalez Add support for Ubuntu 22.04 +- #5125: @abulava Start the vector logging container after Docker daemon restarts +- #5117: @josegonzalez Upgrade to golang 1.17.9 + +### Documentation + +- #5167: @scottpashley Update client documentation to remove reference to unmaintained python client + +### Other + +- #5120: @dependabot[bot] chore(deps): bump github/codeql-action from 1 to 2 +- #5145: @dependabot[bot] chore(deps): bump jinja2 from 3.1.1 to 3.1.2 in /tests/apps/python-flask +- #5157: @dependabot[bot] chore(deps): bump luizm/action-sh-checker from 0.3.0 to 0.4.0 +- #5160: @dependabot[bot] chore(deps): bump docker/setup-buildx-action from 1 to 2 +- #5161: @dependabot[bot] chore(deps): bump docker/login-action from 1 to 2 +- #5162: @dependabot[bot] chore(deps): bump docker/setup-qemu-action from 1 to 2 +- #5153: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/config +- #5152: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/nodejs-express-noappjson +- #5151: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/nodejs-express-noprocfile +- #5150: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/nodejs-express +- #5154: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/gitsubmodules +- #5155: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/.websocket.disabled +- #5163: @dependabot[bot] chore(deps): bump minimist from 1.2.5 to 1.2.6 in /tests/apps/multi +- #5149: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/dockerfile-dokku-scale +- #5148: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/dockerfile-procfile-bad +- #5147: @dependabot[bot] chore(deps): bump express from 4.17.3 to 4.18.1 in /tests/apps/dockerfile-procfile +- #5144: @dependabot[bot] chore(deps): bump flask from 2.1.1 to 2.1.2 in /tests/apps/python-flask +- #5143: @dependabot[bot] chore(deps): bump werkzeug from 2.1.1 to 2.1.2 in /tests/apps/python-flask +- #5142: @dependabot[bot] chore(deps): bump flask from 2.1.1 to 2.1.2 in /tests/apps/multi +- #5137: @dependabot[bot] chore(deps): bump grunt from 1.4.1 to 1.5.2 in /tests/apps/multi +- #5119: @dependabot[bot] chore(deps): bump socket.io from 4.4.1 to 4.5.0 in /tests/apps/.websocket.disabled + +## 0.27.1 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.1/bootstrap.sh +sudo DOKKU_TAG=v0.27.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #5095: @josegonzalez Ensure we properly clone and rename common properties +- #5094: @josegonzalez Handle case when apps:clone is run in a directory the dokku user does not have access to +- #5093: @josegonzalez Fix deploy branch setting +- #5068: @josegonzalez Properly set cron-id from flag + +### New Features + +- #5092: @josegonzalez Warn and exit non-zero when attempting to rebuild from a non-existent branch +- #5103: @naveensrinivasan Included githubactions in the dependabot config + +### Documentation + +- #5090: @josegonzalez Clarify the exact command that is run once a day by cron tasks +- #5085: @josegonzalez Use issue template form functionality on github +- #5076: @stamp-cmd Lint Markdown, Remove unused links +- #5070: @josegonzalez Add discord as support channel + +### Other + +- #5107: @dependabot[bot] chore(deps): bump actions/checkout from 2 to 3 +- #5105: @dependabot[bot] chore(deps): bump ibiqlik/action-yamllint from 3.0.4 to 3.1 +- #5109: @dependabot[bot] chore(deps): bump actions/upload-artifact from 2 to 3 +- #5108: @dependabot[bot] chore(deps): bump hadolint/hadolint-action from 1.6.0 to 2.1.0 +- #5110: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 215 to 216 in /tests/apps/php +- #5104: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 214 to 215 in /tests/apps/php +- #5097: @dependabot[bot] chore(deps): bump werkzeug from 2.1.0 to 2.1.1 in /tests/apps/python-flask +- #5098: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 213 to 214 in /tests/apps/php +- #5096: @dependabot[bot] chore(deps): bump jetty-servlet from 11.0.8 to 11.0.9 in /tests/apps/java +- #5091: @josegonzalez Update all dependencies for multi test app +- #5089: @dependabot[bot] chore(deps): bump minimist from 1.2.5 to 1.2.6 in /tests/apps/multi +- #5081: @dependabot[bot] chore(deps): bump jinja2 from 3.0.3 to 3.1.1 in /tests/apps/python-flask +- #5071: @dependabot[bot] chore(deps): bump monolog/monolog from 1.26.1 to 1.27.0 in /tests/apps/php +- #5075: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 212 to 213 in /tests/apps/php +- #5082: @dependabot[bot] chore(deps): bump werkzeug from 2.0.3 to 2.1.0 in /tests/apps/python-flask +- #5086: @dependabot[bot] chore(deps): bump flask from 2.0.3 to 2.1.1 in /tests/apps/multi +- #5087: @dependabot[bot] chore(deps): bump flask from 2.0.3 to 2.1.1 in /tests/apps/python-flask +- #5067: @dependabot[bot] chore(deps): bump maven-dependency-plugin from 3.2.0 to 3.3.0 in /tests/apps/java + +## 0.27.0 + +Install/update via the bootstrap script: + +```shell +wget -NP . https://dokku.com/install/v0.27.0/bootstrap.sh +sudo DOKKU_TAG=v0.27.0 bash bootstrap.sh +``` + +See the [0.27.0 migration guide](/docs/appendices/0.27.0-migration-guide.md) for more information on migrating to 0.27.0. + +### Bug Fixes + +- #5063: @josegonzalez Fix buildx support for releases +- #5061: @josegonzalez Use correct token when publish unit test results +- #5057: @josegonzalez Ensure we use the correct variable for WAIT +- #5052: @josegonzalez Run crontab under sudo to support rhel systems +- #5051: @josegonzalez Ensure vector component sources have valid names +- #5035: @nerg4l Fix help flag for logs command +- #5019: @josegonzalez Only rename app domains associated with a global domain + +### New Features + +- #5064: @josegonzalez Upgrade docker image to ubuntu focal +- #5058: @josegonzalez Add .shellcheckrc +- #5055: @josegonzalez Release dokku for 32-bit Raspbian Bullseye +- #5050: @josegonzalez Set the default memory unit type to megabytes +- #5017: @josegonzalez Add command to clear all proxy configs + +### Refactors + +- #4921: @josegonzalez Add ability to filter returnable apps + +### Documentation + +- #5056: @miku86 Fix typo in port management docs +- #5018: @josegonzalez Add warning for setting PORT environment variable + +### Other + +- #5062: @dependabot[bot] chore(deps): bump django from 3.1.13 to 3.1.14 in /tests/apps/dockerfile-release +- #5053: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 211 to 212 in /tests/apps/php +- #5038: @dependabot[bot] chore(deps): bump flask from 2.0.2 to 2.0.3 in /tests/apps/multi +- #5048: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 210 to 211 in /tests/apps/php +- #5039: @dependabot[bot] chore(deps): bump sinatra from 2.1.0 to 2.2.0 in /tests/apps/ruby +- #5037: @dependabot[bot] chore(deps): bump flask from 2.0.2 to 2.0.3 in /tests/apps/python-flask +- #5036: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 207 to 210 in /tests/apps/php +- #5025: @dependabot[bot] chore(deps): bump jetty-servlet from 11.0.7 to 11.0.8 in /tests/apps/java +- #5026: @dependabot[bot] chore(deps): bump werkzeug from 2.0.2 to 2.0.3 in /tests/apps/python-flask +- #5027: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 206 to 207 in /tests/apps/php +- #5021: @dependabot[bot] chore(deps-dev): bump heroku/heroku-buildpack-php from 205 to 206 in /tests/apps/php +- #4975: @tcurdt add arm64 to the build + ## 0.26.8 Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.8/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.8/bootstrap.sh sudo DOKKU_TAG=v0.26.8 bash bootstrap.sh ``` @@ -29,7 +5330,7 @@ sudo DOKKU_TAG=v0.26.8 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.7/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.7/bootstrap.sh sudo DOKKU_TAG=v0.26.7 bash bootstrap.sh ``` @@ -69,7 +5370,7 @@ sudo DOKKU_TAG=v0.26.7 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.6/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.6/bootstrap.sh sudo DOKKU_TAG=v0.26.6 bash bootstrap.sh ``` @@ -83,7 +5384,7 @@ sudo DOKKU_TAG=v0.26.6 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.5/bootstrap.sh sudo DOKKU_TAG=v0.26.5 bash bootstrap.sh ``` @@ -111,7 +5412,7 @@ sudo DOKKU_TAG=v0.26.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.4/bootstrap.sh sudo DOKKU_TAG=v0.26.4 bash bootstrap.sh ``` @@ -130,7 +5431,7 @@ sudo DOKKU_TAG=v0.26.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.3/bootstrap.sh sudo DOKKU_TAG=v0.26.3 bash bootstrap.sh ``` @@ -143,7 +5444,7 @@ sudo DOKKU_TAG=v0.26.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.2/bootstrap.sh sudo DOKKU_TAG=v0.26.2 bash bootstrap.sh ``` @@ -161,7 +5462,7 @@ sudo DOKKU_TAG=v0.26.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.1/bootstrap.sh sudo DOKKU_TAG=v0.26.1 bash bootstrap.sh ``` @@ -178,7 +5479,7 @@ sudo DOKKU_TAG=v0.26.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.26.0/bootstrap.sh sudo DOKKU_TAG=v0.26.0 bash bootstrap.sh ``` @@ -221,7 +5522,7 @@ See the [0.26.0 migration guide](/docs/appendices/0.26.0-migration-guide.md) for Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.25.7/bootstrap.sh +wget -NP . https://dokku.com/install/v0.25.7/bootstrap.sh sudo DOKKU_TAG=v0.25.7 bash bootstrap.sh ``` @@ -247,7 +5548,7 @@ sudo DOKKU_TAG=v0.25.7 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.25.6/bootstrap.sh +wget -NP . https://dokku.com/install/v0.25.6/bootstrap.sh sudo DOKKU_TAG=v0.25.6 bash bootstrap.sh ``` @@ -281,7 +5582,7 @@ sudo DOKKU_TAG=v0.25.6 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.25.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.25.5/bootstrap.sh sudo DOKKU_TAG=v0.25.5 bash bootstrap.sh ``` @@ -328,7 +5629,7 @@ sudo DOKKU_TAG=v0.25.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.25.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.25.4/bootstrap.sh sudo DOKKU_TAG=v0.25.4 bash bootstrap.sh ``` @@ -350,7 +5651,7 @@ sudo DOKKU_TAG=v0.25.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.25.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.25.3/bootstrap.sh sudo DOKKU_TAG=v0.25.3 bash bootstrap.sh ``` @@ -376,7 +5677,7 @@ sudo DOKKU_TAG=v0.25.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.25.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.25.2/bootstrap.sh sudo DOKKU_TAG=v0.25.2 bash bootstrap.sh ``` @@ -405,7 +5706,7 @@ sudo DOKKU_TAG=v0.25.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.25.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.25.1/bootstrap.sh sudo DOKKU_TAG=v0.25.1 bash bootstrap.sh ``` @@ -422,7 +5723,7 @@ sudo DOKKU_TAG=v0.25.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.25.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.25.0/bootstrap.sh sudo DOKKU_TAG=v0.25.0 bash bootstrap.sh ``` @@ -507,7 +5808,7 @@ See the [0.25.0 migration guide](/docs/appendices/0.25.0-migration-guide.md) for Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.10/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.10/bootstrap.sh sudo DOKKU_TAG=v0.24.10 bash bootstrap.sh ``` @@ -529,7 +5830,7 @@ sudo DOKKU_TAG=v0.24.10 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.9/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.9/bootstrap.sh sudo DOKKU_TAG=v0.24.9 bash bootstrap.sh ``` @@ -558,7 +5859,7 @@ sudo DOKKU_TAG=v0.24.9 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.8/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.8/bootstrap.sh sudo DOKKU_TAG=v0.24.8 bash bootstrap.sh ``` @@ -590,7 +5891,7 @@ sudo DOKKU_TAG=v0.24.8 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.7/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.7/bootstrap.sh sudo DOKKU_TAG=v0.24.7 bash bootstrap.sh ``` @@ -607,7 +5908,7 @@ sudo DOKKU_TAG=v0.24.7 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.6/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.6/bootstrap.sh sudo DOKKU_TAG=v0.24.6 bash bootstrap.sh ``` @@ -634,7 +5935,7 @@ sudo DOKKU_TAG=v0.24.6 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.5/bootstrap.sh sudo DOKKU_TAG=v0.24.5 bash bootstrap.sh ``` @@ -655,7 +5956,7 @@ sudo DOKKU_TAG=v0.24.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.4/bootstrap.sh sudo DOKKU_TAG=v0.24.4 bash bootstrap.sh ``` @@ -696,7 +5997,7 @@ sudo DOKKU_TAG=v0.24.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.3/bootstrap.sh sudo DOKKU_TAG=v0.24.3 bash bootstrap.sh ``` @@ -720,7 +6021,7 @@ sudo DOKKU_TAG=v0.24.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.2/bootstrap.sh sudo DOKKU_TAG=v0.24.2 bash bootstrap.sh ``` @@ -739,7 +6040,7 @@ sudo DOKKU_TAG=v0.24.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.1/bootstrap.sh sudo DOKKU_TAG=v0.24.1 bash bootstrap.sh ``` @@ -758,7 +6059,7 @@ sudo DOKKU_TAG=v0.24.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.24.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.24.0/bootstrap.sh sudo DOKKU_TAG=v0.24.0 bash bootstrap.sh ``` @@ -792,7 +6093,7 @@ See the [0.24.0 migration guide](/docs/appendices/0.24.0-migration-guide.md) for Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.9/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.9/bootstrap.sh sudo DOKKU_TAG=v0.23.9 bash bootstrap.sh ``` @@ -810,7 +6111,7 @@ sudo DOKKU_TAG=v0.23.9 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.8/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.8/bootstrap.sh sudo DOKKU_TAG=v0.23.8 bash bootstrap.sh ``` @@ -838,7 +6139,7 @@ sudo DOKKU_TAG=v0.23.8 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.7/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.7/bootstrap.sh sudo DOKKU_TAG=v0.23.7 bash bootstrap.sh ``` @@ -867,7 +6168,7 @@ sudo DOKKU_TAG=v0.23.7 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.6/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.6/bootstrap.sh sudo DOKKU_TAG=v0.23.6 bash bootstrap.sh ``` @@ -884,7 +6185,7 @@ sudo DOKKU_TAG=v0.23.6 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.5/bootstrap.sh sudo DOKKU_TAG=v0.23.5 bash bootstrap.sh ``` @@ -905,7 +6206,7 @@ sudo DOKKU_TAG=v0.23.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.4/bootstrap.sh sudo DOKKU_TAG=v0.23.4 bash bootstrap.sh ``` @@ -923,7 +6224,7 @@ sudo DOKKU_TAG=v0.23.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.3/bootstrap.sh sudo DOKKU_TAG=v0.23.3 bash bootstrap.sh ``` @@ -957,7 +6258,7 @@ sudo DOKKU_TAG=v0.23.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.2/bootstrap.sh sudo DOKKU_TAG=v0.23.2 bash bootstrap.sh ``` @@ -989,7 +6290,7 @@ sudo DOKKU_TAG=v0.23.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.1/bootstrap.sh sudo DOKKU_TAG=v0.23.1 bash bootstrap.sh ``` @@ -1019,7 +6320,7 @@ sudo DOKKU_TAG=v0.23.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.23.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.23.0/bootstrap.sh sudo DOKKU_TAG=v0.23.0 bash bootstrap.sh ``` @@ -1069,7 +6370,7 @@ See the [0.23.0 migration guide](/docs/appendices/0.23.0-migration-guide.md) for Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.9/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.9/bootstrap.sh sudo DOKKU_TAG=v0.22.9 bash bootstrap.sh ``` @@ -1111,7 +6412,7 @@ sudo DOKKU_TAG=v0.22.9 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.8/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.8/bootstrap.sh sudo DOKKU_TAG=v0.22.8 bash bootstrap.sh ``` @@ -1128,7 +6429,7 @@ sudo DOKKU_TAG=v0.22.8 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.7/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.7/bootstrap.sh sudo DOKKU_TAG=v0.22.7 bash bootstrap.sh ``` @@ -1141,7 +6442,7 @@ sudo DOKKU_TAG=v0.22.7 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.6/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.6/bootstrap.sh sudo DOKKU_TAG=v0.22.6 bash bootstrap.sh ``` @@ -1175,7 +6476,7 @@ sudo DOKKU_TAG=v0.22.6 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.5/bootstrap.sh sudo DOKKU_TAG=v0.22.5 bash bootstrap.sh ``` @@ -1200,7 +6501,7 @@ sudo DOKKU_TAG=v0.22.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.4/bootstrap.sh sudo DOKKU_TAG=v0.22.4 bash bootstrap.sh ``` @@ -1229,7 +6530,7 @@ sudo DOKKU_TAG=v0.22.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.3/bootstrap.sh sudo DOKKU_TAG=v0.22.3 bash bootstrap.sh ``` @@ -1264,7 +6565,7 @@ sudo DOKKU_TAG=v0.22.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.2/bootstrap.sh sudo DOKKU_TAG=v0.22.2 bash bootstrap.sh ``` @@ -1285,7 +6586,7 @@ sudo DOKKU_TAG=v0.22.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.1/bootstrap.sh sudo DOKKU_TAG=v0.22.1 bash bootstrap.sh ``` @@ -1331,7 +6632,7 @@ sudo DOKKU_TAG=v0.22.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.22.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.22.0/bootstrap.sh sudo DOKKU_TAG=v0.22.0 bash bootstrap.sh ``` @@ -1372,7 +6673,7 @@ See the [0.22.0 migration guide](/docs/appendices/0.22.0-migration-guide.md) for - #4160: @nerg4l Rewrite logs plugin in Go - #4149: @josegonzalez Rewrite ps plugin in golang -- #4080: @hugopeixoto Stop using VHOST when listing app domains and urls +- #4080: @hugopeixoto Stop using VHOST when listing app domains and urls - #4117: @josegonzalez Rewrite app-json plugin in golang - #4113: @josegonzalez Drop herokuish release code @@ -1417,7 +6718,7 @@ See the [0.22.0 migration guide](/docs/appendices/0.22.0-migration-guide.md) for Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.21.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.21.4/bootstrap.sh sudo DOKKU_TAG=v0.21.4 bash bootstrap.sh ``` @@ -1447,7 +6748,7 @@ sudo DOKKU_TAG=v0.21.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.21.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.21.3/bootstrap.sh sudo DOKKU_TAG=v0.21.3 bash bootstrap.sh ``` @@ -1465,7 +6766,7 @@ sudo DOKKU_TAG=v0.21.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.21.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.21.2/bootstrap.sh sudo DOKKU_TAG=v0.21.2 bash bootstrap.sh ``` @@ -1495,7 +6796,7 @@ sudo DOKKU_TAG=v0.21.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.21.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.21.1/bootstrap.sh sudo DOKKU_TAG=v0.21.1 bash bootstrap.sh ``` @@ -1505,7 +6806,8 @@ sudo DOKKU_TAG=v0.21.1 bash bootstrap.sh ## 0.21.0 -> Warning: Release is broken and will be pulled from upstream. Please use a newer version. +> [!WARNING] +> Release is broken and will be pulled from upstream. Please use a newer version. ### Bug Fixes @@ -1596,7 +6898,7 @@ sudo DOKKU_TAG=v0.21.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.20.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.20.4/bootstrap.sh sudo DOKKU_TAG=v0.20.4 bash bootstrap.sh ``` @@ -1619,7 +6921,7 @@ sudo DOKKU_TAG=v0.20.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.20.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.20.3/bootstrap.sh sudo DOKKU_TAG=v0.20.3 bash bootstrap.sh ``` @@ -1638,7 +6940,7 @@ sudo DOKKU_TAG=v0.20.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.20.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.20.2/bootstrap.sh sudo DOKKU_TAG=v0.20.2 bash bootstrap.sh ``` @@ -1656,7 +6958,7 @@ sudo DOKKU_TAG=v0.20.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.20.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.20.1/bootstrap.sh sudo DOKKU_TAG=v0.20.1 bash bootstrap.sh ``` @@ -1687,7 +6989,7 @@ sudo DOKKU_TAG=v0.20.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.20.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.20.0/bootstrap.sh sudo DOKKU_TAG=v0.20.0 bash bootstrap.sh ``` @@ -1760,7 +7062,7 @@ sudo DOKKU_TAG=v0.20.0 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.13/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.13/bootstrap.sh sudo DOKKU_TAG=v0.19.13 bash bootstrap.sh ``` @@ -1777,7 +7079,7 @@ sudo DOKKU_TAG=v0.19.13 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.12/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.12/bootstrap.sh sudo DOKKU_TAG=v0.19.12 bash bootstrap.sh ``` @@ -1809,7 +7111,7 @@ sudo DOKKU_TAG=v0.19.12 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.11/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.11/bootstrap.sh sudo DOKKU_TAG=v0.19.11 bash bootstrap.sh ``` @@ -1830,7 +7132,7 @@ sudo DOKKU_TAG=v0.19.11 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.10/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.10/bootstrap.sh sudo DOKKU_TAG=v0.19.10 bash bootstrap.sh ``` @@ -1871,7 +7173,7 @@ sudo DOKKU_TAG=v0.19.10 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.9/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.9/bootstrap.sh sudo DOKKU_TAG=v0.19.9 bash bootstrap.sh ``` @@ -1891,7 +7193,7 @@ sudo DOKKU_TAG=v0.19.9 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.8/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.8/bootstrap.sh sudo DOKKU_TAG=v0.19.8 bash bootstrap.sh ``` @@ -1900,7 +7202,7 @@ sudo DOKKU_TAG=v0.19.8 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.7/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.7/bootstrap.sh sudo DOKKU_TAG=v0.19.7 bash bootstrap.sh ``` @@ -1918,7 +7220,7 @@ sudo DOKKU_TAG=v0.19.7 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.6/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.6/bootstrap.sh sudo DOKKU_TAG=v0.19.6 bash bootstrap.sh ``` @@ -1933,7 +7235,7 @@ sudo DOKKU_TAG=v0.19.6 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.5/bootstrap.sh sudo DOKKU_TAG=v0.19.5 bash bootstrap.sh ``` @@ -1946,7 +7248,7 @@ sudo DOKKU_TAG=v0.19.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.4/bootstrap.sh sudo DOKKU_TAG=v0.19.4 bash bootstrap.sh ``` @@ -1959,7 +7261,7 @@ sudo DOKKU_TAG=v0.19.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.3/bootstrap.sh sudo DOKKU_TAG=v0.19.3 bash bootstrap.sh ``` @@ -1996,7 +7298,7 @@ sudo DOKKU_TAG=v0.19.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.2/bootstrap.sh sudo DOKKU_TAG=v0.19.2 bash bootstrap.sh ``` @@ -2009,20 +7311,20 @@ sudo DOKKU_TAG=v0.19.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.1/bootstrap.sh sudo DOKKU_TAG=v0.19.1 bash bootstrap.sh ``` ### Bug Fixes -- #3713: @josegonzalez Require nginx 1.13+ in order to enable tls1.3 +- #3713: @josegonzalez Require nginx 1.13+ in order to enable tls1.3 ## 0.19.0 Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.19.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.19.0/bootstrap.sh sudo DOKKU_TAG=v0.19.0 bash bootstrap.sh ``` @@ -2065,7 +7367,7 @@ sudo DOKKU_TAG=v0.19.0 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.18.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.18.5/bootstrap.sh sudo DOKKU_TAG=v0.18.5 bash bootstrap.sh ``` @@ -2078,7 +7380,7 @@ sudo DOKKU_TAG=v0.18.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.18.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.18.4/bootstrap.sh sudo DOKKU_TAG=v0.18.4 bash bootstrap.sh ``` @@ -2091,7 +7393,7 @@ sudo DOKKU_TAG=v0.18.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.18.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.18.3/bootstrap.sh sudo DOKKU_TAG=v0.18.3 bash bootstrap.sh ``` @@ -2116,7 +7418,7 @@ sudo DOKKU_TAG=v0.18.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.18.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.18.2/bootstrap.sh sudo DOKKU_TAG=v0.18.2 bash bootstrap.sh ``` @@ -2138,7 +7440,7 @@ sudo DOKKU_TAG=v0.18.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.18.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.18.1/bootstrap.sh sudo DOKKU_TAG=v0.18.1 bash bootstrap.sh ``` @@ -2152,7 +7454,7 @@ sudo DOKKU_TAG=v0.18.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.18.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.18.0/bootstrap.sh sudo DOKKU_TAG=v0.18.0 bash bootstrap.sh ``` @@ -2194,7 +7496,7 @@ sudo DOKKU_TAG=v0.18.0 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.9/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.9/bootstrap.sh sudo DOKKU_TAG=v0.17.9 bash bootstrap.sh ``` @@ -2211,7 +7513,7 @@ sudo DOKKU_TAG=v0.17.9 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.8/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.8/bootstrap.sh sudo DOKKU_TAG=v0.17.8 bash bootstrap.sh ``` @@ -2228,7 +7530,7 @@ sudo DOKKU_TAG=v0.17.8 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.7/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.7/bootstrap.sh sudo DOKKU_TAG=v0.17.7 bash bootstrap.sh ``` @@ -2249,7 +7551,7 @@ sudo DOKKU_TAG=v0.17.7 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.6/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.6/bootstrap.sh sudo DOKKU_TAG=v0.17.6 bash bootstrap.sh ``` @@ -2262,7 +7564,7 @@ sudo DOKKU_TAG=v0.17.6 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.5/bootstrap.sh sudo DOKKU_TAG=v0.17.5 bash bootstrap.sh ``` @@ -2275,7 +7577,7 @@ sudo DOKKU_TAG=v0.17.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.4/bootstrap.sh sudo DOKKU_TAG=v0.17.4 bash bootstrap.sh ``` @@ -2288,7 +7590,7 @@ sudo DOKKU_TAG=v0.17.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.3/bootstrap.sh sudo DOKKU_TAG=v0.17.3 bash bootstrap.sh ``` @@ -2306,7 +7608,7 @@ sudo DOKKU_TAG=v0.17.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.2/bootstrap.sh sudo DOKKU_TAG=v0.17.2 bash bootstrap.sh ``` @@ -2319,7 +7621,7 @@ sudo DOKKU_TAG=v0.17.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.1/bootstrap.sh sudo DOKKU_TAG=v0.17.1 bash bootstrap.sh ``` @@ -2332,7 +7634,7 @@ sudo DOKKU_TAG=v0.17.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.17.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.17.0/bootstrap.sh sudo DOKKU_TAG=v0.17.0 bash bootstrap.sh ``` @@ -2369,7 +7671,7 @@ sudo DOKKU_TAG=v0.17.0 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.16.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.16.4/bootstrap.sh sudo DOKKU_TAG=v0.16.4 bash bootstrap.sh ``` @@ -2386,7 +7688,7 @@ sudo DOKKU_TAG=v0.16.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.16.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.16.3/bootstrap.sh sudo DOKKU_TAG=v0.16.3 bash bootstrap.sh ``` @@ -2410,7 +7712,7 @@ sudo DOKKU_TAG=v0.16.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.16.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.16.2/bootstrap.sh sudo DOKKU_TAG=v0.16.2 bash bootstrap.sh ``` @@ -2424,7 +7726,7 @@ sudo DOKKU_TAG=v0.16.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.16.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.16.1/bootstrap.sh sudo DOKKU_TAG=v0.16.1 bash bootstrap.sh ``` @@ -2441,7 +7743,7 @@ sudo DOKKU_TAG=v0.16.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.16.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.16.0/bootstrap.sh sudo DOKKU_TAG=v0.16.0 bash bootstrap.sh ``` @@ -2467,7 +7769,7 @@ sudo DOKKU_TAG=v0.16.0 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.15.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.15.5/bootstrap.sh sudo DOKKU_TAG=v0.15.5 bash bootstrap.sh ``` @@ -2486,7 +7788,7 @@ sudo DOKKU_TAG=v0.15.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.15.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.15.4/bootstrap.sh sudo DOKKU_TAG=v0.15.4 bash bootstrap.sh ``` @@ -2513,7 +7815,7 @@ sudo DOKKU_TAG=v0.15.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.15.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.15.3/bootstrap.sh sudo DOKKU_TAG=v0.15.3 bash bootstrap.sh ``` @@ -2526,7 +7828,7 @@ sudo DOKKU_TAG=v0.15.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.15.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.15.2/bootstrap.sh sudo DOKKU_TAG=v0.15.2 bash bootstrap.sh ``` @@ -2539,7 +7841,7 @@ sudo DOKKU_TAG=v0.15.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.15.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.15.1/bootstrap.sh sudo DOKKU_TAG=v0.15.1 bash bootstrap.sh ``` @@ -2552,7 +7854,7 @@ sudo DOKKU_TAG=v0.15.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.15.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.15.0/bootstrap.sh sudo DOKKU_TAG=v0.15.0 bash bootstrap.sh ``` @@ -2590,7 +7892,7 @@ sudo DOKKU_TAG=v0.15.0 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.14.6/bootstrap.sh +wget -NP . https://dokku.com/install/v0.14.6/bootstrap.sh sudo DOKKU_TAG=v0.14.6 bash bootstrap.sh ``` @@ -2621,7 +7923,7 @@ sudo DOKKU_TAG=v0.14.6 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.14.5/bootstrap.sh +wget -NP . https://dokku.com/install/v0.14.5/bootstrap.sh sudo DOKKU_TAG=v0.14.5 bash bootstrap.sh ``` @@ -2634,7 +7936,7 @@ sudo DOKKU_TAG=v0.14.5 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.14.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.14.4/bootstrap.sh sudo DOKKU_TAG=v0.14.4 bash bootstrap.sh ``` @@ -2647,7 +7949,7 @@ sudo DOKKU_TAG=v0.14.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.14.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.14.3/bootstrap.sh sudo DOKKU_TAG=v0.14.3 bash bootstrap.sh ``` @@ -2678,7 +7980,7 @@ sudo DOKKU_TAG=v0.14.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.14.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.14.2/bootstrap.sh sudo DOKKU_TAG=v0.14.2 bash bootstrap.sh ``` @@ -2697,7 +7999,7 @@ sudo DOKKU_TAG=v0.14.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.14.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.14.1/bootstrap.sh sudo DOKKU_TAG=v0.14.1 bash bootstrap.sh ``` @@ -2710,7 +8012,7 @@ sudo DOKKU_TAG=v0.14.1 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.14.0/bootstrap.sh +wget -NP . https://dokku.com/install/v0.14.0/bootstrap.sh sudo DOKKU_TAG=v0.14.0 bash bootstrap.sh ``` @@ -2771,7 +8073,7 @@ sudo DOKKU_TAG=v0.14.0 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.13.4/bootstrap.sh +wget -NP . https://dokku.com/install/v0.13.4/bootstrap.sh sudo DOKKU_TAG=v0.13.4 bash bootstrap.sh ``` @@ -2788,7 +8090,7 @@ sudo DOKKU_TAG=v0.13.4 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.13.3/bootstrap.sh +wget -NP . https://dokku.com/install/v0.13.3/bootstrap.sh sudo DOKKU_TAG=v0.13.3 bash bootstrap.sh ``` @@ -2805,7 +8107,7 @@ sudo DOKKU_TAG=v0.13.3 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.13.2/bootstrap.sh +wget -NP . https://dokku.com/install/v0.13.2/bootstrap.sh sudo DOKKU_TAG=v0.13.2 bash bootstrap.sh ``` @@ -2823,7 +8125,7 @@ sudo DOKKU_TAG=v0.13.2 bash bootstrap.sh Install/update via the bootstrap script: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/v0.13.1/bootstrap.sh +wget -NP . https://dokku.com/install/v0.13.1/bootstrap.sh sudo DOKKU_TAG=v0.13.1 bash bootstrap.sh ``` @@ -3381,7 +8683,7 @@ sudo DOKKU_TAG=v0.13.1 bash bootstrap.sh - #2683: @josegonzalez Ensure we have an example for adding keys as another user - #2682: @josegonzalez Clarify supported stanzas in app.json - #2679: @callahad Remove unnecessary Linode-specific instructions -- #2670: @znz Remove duplicated `(i.e. ` +- #2670: @znz Remove duplicated `(i.e.` ### Documentation @@ -3735,7 +9037,7 @@ Thanks to all the contributors who helped with this release, and a special thank ## 0.5.8 -This release is the last release in the `0.5.x` series, and as such is mainly a bugfix release. Users are highly encouraged to upgrade to this release *before* moving to the upcoming `0.6.x` release, as we will be removing deprecated features at that point. +This release is the last release in the `0.5.x` series, and as such is mainly a bugfix release. Users are highly encouraged to upgrade to this release _before_ moving to the upcoming `0.6.x` release, as we will be removing deprecated features at that point. Thanks to all the contributors who helped with this release! @@ -3927,7 +9229,7 @@ That was quick! This is a bugfix release to fix issues in the packaging and rele This is our largest, most feature-packed release in the history of the dokku project. Lots of delicious things, including: -- Support for docker 1.10/1.11. You *must* have docker 1.9.1+ to install dokku. +- Support for docker 1.10/1.11. You _must_ have docker 1.9.1+ to install dokku. - Revamped documentation website - [Deployment Tasks](https://dokku.com/docs/advanced-usage/deployment-tasks/) - Heroku-style management of [dockerfile processes](https://dokku.com/docs/deployment/builders/dockerfiles/#procfiles-and-multiple-processes) @@ -4026,7 +9328,7 @@ Thanks to all the contributors who helped with this release! ## 0.4.13 -We lied. *This* is the final 0.4.x release. This specific release fixes support for bash `4.2`, which may be the only bash version available for certain testing environments. +We lied. _This_ is the final 0.4.x release. This specific release fixes support for bash `4.2`, which may be the only bash version available for certain testing environments. Thanks to all the contributors who helped with this release! @@ -4080,7 +9382,7 @@ Thanks to all the contributors who helped with this release! This release is mostly a bugfix release, though we have a few important changes: - `dokku plugin:update` can now be used to update a specific plugin. Previously, this could potentially result in an error a user would have to manually resolve. -- We have started labeling all dokku-managed containers. In a future minor release, triggering a `dokku cleanup` will remove *only* exited containers that are managed by dokku. This change allows users to start containers outside of dokku and be assured that dokku would not inadvertently remove them. +- We have started labeling all dokku-managed containers. In a future minor release, triggering a `dokku cleanup` will remove _only_ exited containers that are managed by dokku. This change allows users to start containers outside of dokku and be assured that dokku would not inadvertently remove them. Thanks to all the contributors who helped with this release! @@ -4426,11 +9728,11 @@ One new feature is colorized logging output, which should make it easier to debu This is our first minor release in almost a year. Many new features and removals have occurred, so here is a neat summary: - Plugins are now triggered via `plugn`. Notably, you'll need add a `plugin.toml` to describe the plugin as well as use `plugn trigger` instead of `pluginhook` to trigger plugin callbacks. Please see the [plugin creation documentation](https://dokku.com/docs/development/plugin-creation/) for more details. -- A few new official plugins have been added to the core, including [image tagging](https://dokku.com/docs/deployment/application-deployment/), [certificate management](https://dokku.com/docs/configuration/ssl/), a tar-based deploy solution, and much more. Check out the *New Features* section for more details. +- A few new official plugins have been added to the core, including [image tagging](https://dokku.com/docs/deployment/application-deployment/), [certificate management](https://dokku.com/docs/configuration/ssl/), a tar-based deploy solution, and much more. Check out the _New Features_ section for more details. - We've removed a few deprecated plugin callbacks. Please see the [plugin triggers documentation](https://dokku.com/docs/development/plugin-triggers/) to see what is available. - [Official datastorage plugins](https://github.com/dokku) have been created for the most commonly used datastores. If you previously used/maintained a community contributed plugin, please check these out. We'll be adding more official plugins as time goes on. -Thanks to the *many* contributors for making this release our best release so far, and special thanks to both @michaelshobbs and @Flink for pushing along the `0.4.0` release! +Thanks to the _many_ contributors for making this release our best release so far, and special thanks to both @michaelshobbs and @Flink for pushing along the `0.4.0` release! ### Deprecations and Removals @@ -4752,10 +10054,10 @@ This release pegs Dokku to Docker 1.6.2. Docker 1.7.0 introduced changes in `doc - #1028: @Flink [docs] Add rails-logs to plugins - #1031: @michaelshobbs Upgrade docker in CI to 1.5.0 - #1029: @assaf Added several enhancements for CHECKS file: - - Specify how long to wait before running first check - - Specify timeout for each check - - Check specific hosts, e.g. http://signin.example.com - - Check both HTTP and HTTPS resources + - Specify how long to wait before running first check + - Specify timeout for each check + - Check specific hosts, e.g. [http://signin.example.com](http://signin.example.com) + - Check both HTTP and HTTPS resources - #1032: @cameron-martin Updated dokku-installer to use relative path - #1035: @Flink [docs] Add dokku-http-auth to plugins - #1040: @ebeigarts [docs] Add dokku-slack plugin information @@ -4859,12 +10161,12 @@ This release pegs Dokku to Docker 1.6.2. Docker 1.7.0 introduced changes in `doc ## 0.3.9 - #787: @josegonzalez/@michaelshobbs Official user-env-compile plugin - - Uses ENV and APP/ENV files - - Supports old `BUILD_ENV` files (which are likely in wide-use) - - Allows user's to override globals with app-specific configuration - - Migrate `$DOKKU_ROOT/BUILD_ENV` to `$DOKKU_ROOT/ENV` if the former exists and the latter does not - - Drop `BUILD_ENV` support in favor of just `ENV` via a `mv` command - - Add default ENV with `CURL_TIMEOUT` and `CURL_CONNECT_TIMEOUT` + - Uses ENV and APP/ENV files + - Supports old `BUILD_ENV` files (which are likely in wide-use) + - Allows user's to override globals with app-specific configuration + - Migrate `$DOKKU_ROOT/BUILD_ENV` to `$DOKKU_ROOT/ENV` if the former exists and the latter does not + - Drop `BUILD_ENV` support in favor of just `ENV` via a `mv` command + - Add default ENV with `CURL_TIMEOUT` and `CURL_CONNECT_TIMEOUT` - #811: @abossard Increased `server_names_hash_bucket_size` in nginx.conf to 512 - #814: @josegonzalez Source files in $DOKKU_ROOT/.dokkurc directory and add `dokku trace` command - #816: @josegonzalez Add documentation for user-env feature @@ -4973,25 +10275,25 @@ This release pegs Dokku to Docker 1.6.2. Docker 1.7.0 introduced changes in `doc ## 0.2.0 (2013-11-24) -* Added DOKKU_TRACE variable for verbose trace information -* Added an installer (for pre-built images) -* Application config (environment variable management) -* Backup/import plugin -* Basic hooks/plugin system -* Cache dir is preserved across builds -* Command to delete an application -* Exposed commands over SSH using sshcommand -* Git handling is moved to a plugin -* Integration test coverage -* Pulled nginx vhosts out into plugin -* Run command -* Separated dokku and buildstep more cleanly -* Uses latest version of Docker again +- Added DOKKU_TRACE variable for verbose trace information +- Added an installer (for pre-built images) +- Application config (environment variable management) +- Backup/import plugin +- Basic hooks/plugin system +- Cache dir is preserved across builds +- Command to delete an application +- Exposed commands over SSH using sshcommand +- Git handling is moved to a plugin +- Integration test coverage +- Pulled nginx vhosts out into plugin +- Run command +- Separated dokku and buildstep more cleanly +- Uses latest version of Docker again ## 0.1.0 (2013-06-15) - * First release - * Bootstrap script for Ubuntu system - * Basic push / deploy with git - * Hostname support with Nginx - * Support for Java, Ruby, Node.js buildpacks +- First release + - Bootstrap script for Ubuntu system + - Basic push / deploy with git + - Hostname support with Nginx + - Support for Java, Ruby, Node.js buildpacks diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index a2799d88f84..73adb6ecf9c 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -2,7 +2,7 @@ > > If you'd like to sponsor specific functionality, see the project's [Sponsoring](https://github.com/dokku/.github/blob/master/SPONSORING.md) document. > -> If you need help figuring out how to use a specific buildpack, are seeing an issue in during the buildpack process, or are having issues using multiple buildpacks, please see our [slack channels or github discussion board](https://dokku.com/docs/getting-started/where-to-get-help/#monitored-locations). Issues pertaining to buildpacks may be closed and locked. +> If you need help figuring out how to use a specific buildpack, are seeing an issue in during the buildpack process, or are having issues using multiple buildpacks, please see our [chat channels or github discussion board](https://dokku.com/docs/getting-started/where-to-get-help/#monitored-locations). Issues pertaining to buildpacks may be closed and locked. > > If you need support for a version of Dokku that is more than a year old, your issue may be closed without an answer. Please upgrade to a recent version. @@ -31,7 +31,7 @@ > For problems affecting all applications, the report output for a broken application is useful for our debugging. > In these cases, you may run `dokku report` without any arguments to display the top-level reporting information. -### How (deb/make/rpm) and where (AWS, VirtualBox, physical, etc.) was Dokku installed? +### How (deb/make) and where (AWS, VirtualBox, physical, etc.) was Dokku installed? ### Additional information @@ -39,10 +39,10 @@ - The nginx configuration (if applicable) via `dokku nginx:show-config APP_NAME` - Link to the exact repository being deployed (if possible/applicable): - If a deploy is failing or behaving unexpectedly: - - Application name - - The type of application being deployed (node, php, python, ruby, etc.) - - If using buildpacks, which custom buildpacks are in use - - If using a `Dockerfile`, the contents of that file - - If it exists, the contents of your `Procfile`. + - Application name + - The type of application being deployed (node, php, python, ruby, etc.) + - If using buildpacks, which custom buildpacks are in use + - If using a `Dockerfile`, the contents of that file + - If it exists, the contents of your `Procfile`. - Output of failing Dokku commands after running `dokku trace:on` (BEWARE: `trace:on` will print environment variables for some commands, be sure you're not exposing any sensitive information when posting issues. You may replace these values with XXXXXX): diff --git a/Makefile b/Makefile index a6e9b3972bd..6fafe4b3c28 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,17 @@ DOKKU_VERSION ?= master -DOCKER_IMAGE_LABELER_VERSION ?= 0.2.2 -HEROKUISH_VERSION ?= 0.5.25 -NETRC_VERSION ?= 0.3.0 -PLUGN_VERSION ?= 0.6.1 -PROCFILE_VERSION ?= 0.11.0 -SIGIL_VERSION ?= 0.6.0 -SSHCOMMAND_VERSION ?= 0.12.0 -DOCKER_IMAGE_LABELER_URL ?= https://github.com/dokku/docker-image-labeler/releases/download/v${DOCKER_IMAGE_LABELER_VERSION}/docker-image-labeler_${DOCKER_IMAGE_LABELER_VERSION}_linux_x86_64.tgz -NETRC_URL ?= https://github.com/dokku/netrc/releases/download/v${NETRC_VERSION}/netrc_${NETRC_VERSION}_linux_x86_64.tgz -PLUGN_URL ?= https://github.com/dokku/plugn/releases/download/v${PLUGN_VERSION}/plugn_${PLUGN_VERSION}_linux_x86_64.tgz -PROCFILE_UTIL_URL ?= https://github.com/josegonzalez/go-procfile-util/releases/download/v${PROCFILE_VERSION}/procfile-util_${PROCFILE_VERSION}_linux_x86_64.tgz -SIGIL_URL ?= https://github.com/gliderlabs/sigil/releases/download/v${SIGIL_VERSION}/sigil_${SIGIL_VERSION}_Linux_x86_64.tgz -SSHCOMMAND_URL ?= https://github.com/dokku/sshcommand/releases/download/v${SSHCOMMAND_VERSION}/sshcommand_${SSHCOMMAND_VERSION}_linux_x86_64.tgz +TARGETARCH ?= amd64 + +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) +LAMBDA_BUILDER_URL ?= $(shell jq -r --arg name lambda-builder --arg arch $(TARGETARCH) '.dependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json) +NETRC_URL ?= $(shell jq -r --arg name netrc --arg arch $(TARGETARCH) '.dependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json) +PLUGN_URL ?= $(shell jq -r --arg name plugn --arg arch $(TARGETARCH) '.predependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json) +PROCFILE_UTIL_URL ?= $(shell jq -r --arg name procfile-util --arg arch $(TARGETARCH) '.dependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json) +SIGIL_URL ?= $(shell jq -r --arg name gliderlabs-sigil --arg arch $(TARGETARCH) '.predependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json) +SSHCOMMAND_URL ?= $(shell jq -r --arg name sshcommand --arg arch $(TARGETARCH) '.dependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json) STACK_URL ?= https://github.com/gliderlabs/herokuish.git -PREBUILT_STACK_URL ?= gliderlabs/herokuish:latest-20 +PREBUILT_STACK_URL ?= gliderlabs/herokuish:latest-24 DOKKU_LIB_ROOT ?= /var/lib/dokku PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/plugins CORE_PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/core-plugins @@ -36,12 +33,12 @@ endif include common.mk -.PHONY: all apt-update install version copyfiles copyplugin man-db plugins dependencies docker-image-labeler 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 +include docs.mk include tests.mk include package.mk include deb.mk -include rpm.mk include arch.mk all: @@ -57,7 +54,7 @@ go-build: basedir=$(PWD); \ for dir in plugins/*; do \ if [ -e $$dir/Makefile ]; then \ - $(MAKE) -e -C $$dir $(PLUGIN_MAKE_TARGET) || exit $$? ;\ + GO_ARGS='$(GO_ARGS)' CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) GOWORK=off $(MAKE) -e -C $$dir $(PLUGIN_MAKE_TARGET) || exit $$? ;\ fi ;\ done @@ -67,7 +64,7 @@ ifndef PLUGIN_NAME $(error PLUGIN_NAME not specified) endif if [ -e plugins/$(PLUGIN_NAME)/Makefile ]; then \ - $(MAKE) -e -C plugins/$(PLUGIN_NAME) $(PLUGIN_MAKE_TARGET) || exit $$? ;\ + GO_ARGS='$(GO_ARGS)' CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) GOWORK=off $(MAKE) -e -C plugins/$(PLUGIN_NAME) $(PLUGIN_MAKE_TARGET) || exit $$? ;\ fi go-clean: @@ -78,9 +75,12 @@ go-clean: fi ;\ done -copyfiles: - $(MAKE) go-build || exit 1 +copydokku: cp dokku /usr/local/bin/dokku + chmod 0755 /usr/local/bin/dokku + +copyfiles: copydokku + $(MAKE) go-build || exit 1 mkdir -p ${CORE_PLUGINS_PATH} ${PLUGINS_PATH} rm -rf ${CORE_PLUGINS_PATH}/* test -d ${CORE_PLUGINS_PATH}/enabled || PLUGIN_PATH=${CORE_PLUGINS_PATH} plugn init @@ -131,7 +131,7 @@ plugin-dependencies: plugn procfile-util plugins: plugn procfile-util docker sudo -E dokku plugin:install --core -dependencies: apt-update docker-image-labeler netrc sshcommand plugn procfile-util docker help2man man-db sigil dos2unix jq parallel +dependencies: apt-update 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: @@ -153,28 +153,36 @@ man-db: apt-get -qq -y --no-install-recommends install man-db docker-image-labeler: - wget -qO /tmp/docker-image-labeler_latest.tgz ${DOCKER_IMAGE_LABELER_URL} - tar xzf /tmp/docker-image-labeler_latest.tgz -C /usr/local/bin + wget -qO /usr/local/bin/docker-image-labeler ${DOCKER_IMAGE_LABELER_URL} + chmod +x /usr/local/bin/docker-image-labeler + +docker-container-healthchecker: + wget -qO /usr/local/bin/docker-container-healthchecker ${DOCKER_CONTAINER_HEALTHCHECKER_URL} + chmod +x /usr/local/bin/docker-container-healthchecker + +lambda-builder: + wget -qO /usr/local/bin/lambda-builder ${LAMBDA_BUILDER_URL} + chmod +x /usr/local/bin/lambda-builder netrc: - wget -qO /tmp/netrc_latest.tgz ${NETRC_URL} - tar xzf /tmp/netrc_latest.tgz -C /usr/local/bin + wget -qO /usr/local/bin/netrc ${NETRC_URL} + chmod +x /usr/local/bin/netrc procfile-util: - wget -qO /tmp/procfile-util_latest.tgz ${PROCFILE_UTIL_URL} - tar xzf /tmp/procfile-util_latest.tgz -C /usr/local/bin + wget -qO /usr/local/bin/procfile-util ${PROCFILE_UTIL_URL} + chmod +x /usr/local/bin/procfile-util plugn: - wget -qO /tmp/plugn_latest.tgz ${PLUGN_URL} - tar xzf /tmp/plugn_latest.tgz -C /usr/local/bin + wget -qO /usr/local/bin/plugn ${PLUGN_URL} + chmod +x /usr/local/bin/plugn sigil: - wget -qO /tmp/sigil_latest.tgz ${SIGIL_URL} - tar xzf /tmp/sigil_latest.tgz -C /usr/local/bin + wget -qO /usr/local/bin/sigil ${SIGIL_URL} + chmod +x /usr/local/bin/sigil sshcommand: - wget -qO /tmp/sshcommand_latest.tgz ${SSHCOMMAND_URL} - tar xzf /tmp/sshcommand_latest.tgz -C /usr/local/bin + wget -qO /usr/local/bin/sshcommand ${SSHCOMMAND_URL} + chmod +x /usr/local/bin/sshcommand sshcommand create dokku /usr/local/bin/dokku docker: @@ -218,3 +226,8 @@ vagrant-acl-add: vagrant-dokku: vagrant ssh -- "sudo -H -u root bash -c 'dokku $(RUN_ARGS)'" + +IMAGE_TAG ?= dokku/dokku:latest + +test-image-healthcheck: + IMAGE_TAG=$(IMAGE_TAG) ./tests/image/healthcheck.sh diff --git a/README.md b/README.md index 7dfdbd7aed1..45e993060d2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Dokku -[![Build Status](https://github.com/dokku/dokku/workflows/CI/badge.svg)](https://github.com/dokku/dokku/actions?query=workflow%3ACI) +[![Build Status](https://github.com/dokku/dokku/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dokku/dokku/actions?query=branch%3Amaster) [![Ubuntu Package](https://img.shields.io/badge/package-ubuntu-brightgreen.svg?style=flat-square "Ubuntu Package")](https://packagecloud.io/dokku/dokku) [![Arch Package](https://img.shields.io/badge/package-arch-brightgreen.svg?style=flat-square "Arch Package")](https://aur.archlinux.org/packages/dokku/) -[![Slack Group](https://img.shields.io/badge/irc-slack-blue.svg?style=flat-square "Slack Group")](https://glider-slackin.herokuapp.com/) +[![Slack Group](https://img.shields.io/badge/irc-slack-blue.svg?style=flat-square "Slack Group")](https://slack.dokku.com/) [![Documentation](https://img.shields.io/badge/docs-site-blue.svg?style=flat-square "Site")](https://dokku.com/docs/getting-started/installation/) [![OpenCollective](https://opencollective.com/dokku/sponsors/badge.svg?style=flat-square)](#sponsors) [![OpenCollective](https://opencollective.com/dokku/backers/badge.svg?style=flat-square)](#backers) @@ -15,80 +15,67 @@ Docker powered mini-Heroku. The smallest PaaS implementation you've ever seen. Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/dokku#sponsor)] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[![OpenCollective Sponsor 0](https://opencollective.com/dokku/sponsor/0/avatar.svg)](https://opencollective.com/dokku/sponsor/0/website) +[![OpenCollective Sponsor 1](https://opencollective.com/dokku/sponsor/1/avatar.svg)](https://opencollective.com/dokku/sponsor/1/website) +[![OpenCollective Sponsor 2](https://opencollective.com/dokku/sponsor/2/avatar.svg)](https://opencollective.com/dokku/sponsor/2/website) +[![OpenCollective Sponsor 3](https://opencollective.com/dokku/sponsor/3/avatar.svg)](https://opencollective.com/dokku/sponsor/3/website) +[![OpenCollective Sponsor 4](https://opencollective.com/dokku/sponsor/4/avatar.svg)](https://opencollective.com/dokku/sponsor/4/website) +[![OpenCollective Sponsor 5](https://opencollective.com/dokku/sponsor/5/avatar.svg)](https://opencollective.com/dokku/sponsor/5/website) +[![OpenCollective Sponsor 6](https://opencollective.com/dokku/sponsor/6/avatar.svg)](https://opencollective.com/dokku/sponsor/6/website) +[![OpenCollective Sponsor 7](https://opencollective.com/dokku/sponsor/7/avatar.svg)](https://opencollective.com/dokku/sponsor/7/website) +[![OpenCollective Sponsor 8](https://opencollective.com/dokku/sponsor/8/avatar.svg)](https://opencollective.com/dokku/sponsor/8/website) +[![OpenCollective Sponsor 9](https://opencollective.com/dokku/sponsor/9/avatar.svg)](https://opencollective.com/dokku/sponsor/9/website) +[![OpenCollective Sponsor 10](https://opencollective.com/dokku/sponsor/10/avatar.svg)](https://opencollective.com/dokku/sponsor/10/website) +[![OpenCollective Sponsor 11](https://opencollective.com/dokku/sponsor/11/avatar.svg)](https://opencollective.com/dokku/sponsor/11/website) +[![OpenCollective Sponsor 12](https://opencollective.com/dokku/sponsor/12/avatar.svg)](https://opencollective.com/dokku/sponsor/12/website) +[![OpenCollective Sponsor 13](https://opencollective.com/dokku/sponsor/13/avatar.svg)](https://opencollective.com/dokku/sponsor/13/website) +[![OpenCollective Sponsor 14](https://opencollective.com/dokku/sponsor/14/avatar.svg)](https://opencollective.com/dokku/sponsor/14/website) +[![OpenCollective Sponsor 15](https://opencollective.com/dokku/sponsor/15/avatar.svg)](https://opencollective.com/dokku/sponsor/5/website) +[![OpenCollective Sponsor 6](https://opencollective.com/dokku/sponsor/16/avatar.svg)](https://opencollective.com/dokku/sponsor/16/website) +[![OpenCollective Sponsor 17](https://opencollective.com/dokku/sponsor/17/avatar.svg)](https://opencollective.com/dokku/sponsor/17/website) +[![OpenCollective Sponsor 18](https://opencollective.com/dokku/sponsor/18/avatar.svg)](https://opencollective.com/dokku/sponsor/18/website) ## Backers Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/dokku#backer)] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +[![OpenCollective Backer 0](https://opencollective.com/dokku/backer/0/avatar.svg)](https://opencollective.com/dokku/backer/0/website) +[![OpenCollective Backer 1](https://opencollective.com/dokku/backer/1/avatar.svg)](https://opencollective.com/dokku/backer/1/website) +[![OpenCollective Backer 2](https://opencollective.com/dokku/backer/2/avatar.svg)](https://opencollective.com/dokku/backer/2/website) +[![OpenCollective Backer 3](https://opencollective.com/dokku/backer/3/avatar.svg)](https://opencollective.com/dokku/backer/3/website) +[![OpenCollective Backer 4](https://opencollective.com/dokku/backer/4/avatar.svg)](https://opencollective.com/dokku/backer/4/website) +[![OpenCollective Backer 5](https://opencollective.com/dokku/backer/5/avatar.svg)](https://opencollective.com/dokku/backer/5/website) +[![OpenCollective Backer 6](https://opencollective.com/dokku/backer/6/avatar.svg)](https://opencollective.com/dokku/backer/6/website) +[![OpenCollective Backer 7](https://opencollective.com/dokku/backer/7/avatar.svg)](https://opencollective.com/dokku/backer/7/website) +[![OpenCollective Backer 8](https://opencollective.com/dokku/backer/8/avatar.svg)](https://opencollective.com/dokku/backer/8/website) +[![OpenCollective Backer 9](https://opencollective.com/dokku/backer/9/avatar.svg)](https://opencollective.com/dokku/backer/9/website) +[![OpenCollective Backer 10](https://opencollective.com/dokku/backer/10/avatar.svg)](https://opencollective.com/dokku/backer/10/website) +[![OpenCollective Backer 11](https://opencollective.com/dokku/backer/11/avatar.svg)](https://opencollective.com/dokku/backer/11/website) +[![OpenCollective Backer 12](https://opencollective.com/dokku/backer/12/avatar.svg)](https://opencollective.com/dokku/backer/12/website) +[![OpenCollective Backer 13](https://opencollective.com/dokku/backer/13/avatar.svg)](https://opencollective.com/dokku/backer/13/website) +[![OpenCollective Backer 14](https://opencollective.com/dokku/backer/14/avatar.svg)](https://opencollective.com/dokku/backer/14/website) +[![OpenCollective Backer 15](https://opencollective.com/dokku/backer/15/avatar.svg)](https://opencollective.com/dokku/backer/15/website) +[![OpenCollective Backer 16](https://opencollective.com/dokku/backer/16/avatar.svg)](https://opencollective.com/dokku/backer/16/website) +[![OpenCollective Backer 17](https://opencollective.com/dokku/backer/17/avatar.svg)](https://opencollective.com/dokku/backer/17/website) +[![OpenCollective Backer 18](https://opencollective.com/dokku/backer/18/avatar.svg)](https://opencollective.com/dokku/backer/18/website) +[![OpenCollective Backer 19](https://opencollective.com/dokku/backer/19/avatar.svg)](https://opencollective.com/dokku/backer/19/website) +[![OpenCollective Backer 20](https://opencollective.com/dokku/backer/20/avatar.svg)](https://opencollective.com/dokku/backer/20/website) +[![OpenCollective Backer 21](https://opencollective.com/dokku/backer/21/avatar.svg)](https://opencollective.com/dokku/backer/21/website) +[![OpenCollective Backer 22](https://opencollective.com/dokku/backer/22/avatar.svg)](https://opencollective.com/dokku/backer/22/website) +[![OpenCollective Backer 23](https://opencollective.com/dokku/backer/23/avatar.svg)](https://opencollective.com/dokku/backer/23/website) +[![OpenCollective Backer 24](https://opencollective.com/dokku/backer/24/avatar.svg)](https://opencollective.com/dokku/backer/24/website) +[![OpenCollective Backer 25](https://opencollective.com/dokku/backer/25/avatar.svg)](https://opencollective.com/dokku/backer/25/website) +[![OpenCollective Backer 26](https://opencollective.com/dokku/backer/26/avatar.svg)](https://opencollective.com/dokku/backer/26/website) +[![OpenCollective Backer 27](https://opencollective.com/dokku/backer/27/avatar.svg)](https://opencollective.com/dokku/backer/27/website) +[![OpenCollective Backer 28](https://opencollective.com/dokku/backer/28/avatar.svg)](https://opencollective.com/dokku/backer/28/website) +[![OpenCollective Backer 29](https://opencollective.com/dokku/backer/29/avatar.svg)](https://opencollective.com/dokku/backer/29/website) ## Requirements A fresh VM running any of the following operating systems: -- Ubuntu 18.04/20.04 x64 - Any currently supported release -- Debian 9+ x64 -- CentOS 7 x64 *(experimental)* -- Arch Linux x64 *(experimental)* +- Ubuntu 22.04 / 24.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. Otherwise, you will need to import the keypair manually after installation using `dokku ssh-keys:add`. @@ -98,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 https://raw.githubusercontent.com/dokku/dokku/v0.26.8/bootstrap.sh -sudo DOKKU_TAG=v0.26.8 bash bootstrap.sh +wget -NP . https://dokku.com/install/v0.38.25/bootstrap.sh +sudo DOKKU_TAG=v0.38.25 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. @@ -112,15 +99,15 @@ If you wish for a more unattended installation method, see [these](https://dokku ## Documentation -Full documentation - including advanced installation docs - are available online at [https://dokku.com/docs/getting-started/installation/](https://dokku.com/docs/getting-started/installation/). +Full documentation - including advanced installation docs - are available online at . ## Support -You can use [GitHub Issues](https://github.com/dokku/dokku/issues), check [Troubleshooting](https://dokku.com/docs/getting-started/troubleshooting/) in the documentation, or join us on [Gliderlabs Slack in the #dokku channel](https://glider-slackin.herokuapp.com/). +You can use [GitHub Issues](https://github.com/dokku/dokku/issues), check [Troubleshooting](https://dokku.com/docs/getting-started/troubleshooting/) in the documentation, or join us on [Gliderlabs Slack in the #dokku channel](https://slack.dokku.com/). ## Contribution -After checking [GitHub Issues](https://github.com/dokku/dokku/issues), the [Troubleshooting Guide](https://dokku.com/docs/getting-started/troubleshooting/) or having a chat with us on [Gliderlabs Slack in the #dokku channel](https://glider-slackin.herokuapp.com/), feel free to fork and create a Pull Request. +After checking [GitHub Issues](https://github.com/dokku/dokku/issues), the [Troubleshooting Guide](https://dokku.com/docs/getting-started/troubleshooting/) or having a chat with us on [Gliderlabs Slack in the #dokku channel](https://slack.dokku.com/), feel free to fork and create a Pull Request. While we may not merge your PR as is, they serve to start conversations and improve the general Dokku experience for all users. diff --git a/Vagrantfile b/Vagrantfile index d461f02a0cb..1c4552eaa2f 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,16 +1,15 @@ # -*- mode: ruby -*- # vi: set ft=ruby : -BOX_NAME = ENV["BOX_NAME"] || "bento/ubuntu-18.04" +BOX_NAME = ENV["BOX_NAME"] || "bento/ubuntu-24.04" BOX_CPUS = ENV["BOX_CPUS"] || "1" BOX_MEMORY = ENV["BOX_MEMORY"] || "1024" DOKKU_DOMAIN = ENV["DOKKU_DOMAIN"] || "dokku.me" DOKKU_IP = ENV["DOKKU_IP"] || "10.0.0.2" -FORWARDED_PORT = (ENV["FORWARDED_PORT"] || '8080').to_i 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 = "DEBIAN_FRONTEND=noninteractive make -e install" +make_cmd = "TARGETARCH=$(dpkg --print-architecture) DEBIAN_FRONTEND=noninteractive make -e install" if PREBUILT_STACK_URL make_cmd = "PREBUILT_STACK_URL='#{PREBUILT_STACK_URL}' #{make_cmd}" end @@ -40,10 +39,11 @@ Vagrant::configure("2") do |config| end config.vm.define "empty", autostart: false + config.vm.synced_folder ".", "/vagrant", type: "nfs", nfs_version: 4, nfs_udp: false + config.vm.define "dokku", primary: true do |vm| - vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku" - vm.vm.network :forwarded_port, guest: 80, host: FORWARDED_PORT + vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku", type: "nfs", nfs_version: 4, nfs_udp: false vm.vm.hostname = "#{DOKKU_DOMAIN}" vm.vm.network :private_network, ip: DOKKU_IP @@ -55,7 +55,7 @@ Vagrant::configure("2") do |config| vb.customize ["modifyvm", :id, "--cableconnected1", "on"] end - vm.vm.provision :shell, :inline => "export DEBIAN_FRONTEND=noninteractive && apt-get update -qq >/dev/null && apt-get -qq -y --no-install-recommends install git build-essential >/dev/null && cd /root/dokku && #{make_cmd}" + vm.vm.provision :shell, :inline => "export DEBIAN_FRONTEND=noninteractive && apt-get update -qq >/dev/null && apt-get -qq -y --no-install-recommends install git build-essential jq nginx >/dev/null && cd /root/dokku && #{make_cmd}" vm.vm.provision :shell do |s| s.inline = <<-EOT echo '"\e[5~": history-search-backward' > /root/.inputrc @@ -72,46 +72,33 @@ Vagrant::configure("2") do |config| # For windows users. Sharing folder from windows creates problem with sym links and so, sync the repo instead from GOS. config.vm.define "dokku-windows", autostart: false do |vm| - vm.vm.network :forwarded_port, guest: 80, host: FORWARDED_PORT vm.vm.hostname = "#{DOKKU_DOMAIN}" vm.vm.network :private_network, ip: DOKKU_IP - vm.vm.provision :shell, :inline => "export DEBIAN_FRONTEND=noninteractive && apt-get update -qq >/dev/null && apt-get -qq -y --no-install-recommends install git dos2unix >/dev/null" + vm.vm.provision :shell, :inline => "export DEBIAN_FRONTEND=noninteractive && apt-get update -qq >/dev/null && apt-get -qq -y --no-install-recommends install git dos2unix nginx >/dev/null" vm.vm.provision :shell, :inline => "cd /vagrant/ && export DOKKU_BRANCH=`git symbolic-ref -q --short HEAD 2>/dev/null` && export DOKKU_TAG=`git describe --tags --exact-match 2>/dev/null` && cd /root/ && cp /vagrant/bootstrap.sh ./ && dos2unix bootstrap.sh && bash bootstrap.sh" end config.vm.define "dokku-deb", autostart: false do |vm| - vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku" - vm.vm.network :forwarded_port, guest: 80, host: FORWARDED_PORT + vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku", type: "nfs", nfs_version: 4, nfs_udp: false vm.vm.hostname = "#{DOKKU_DOMAIN}" vm.vm.network :private_network, ip: DOKKU_IP vm.vm.provision :shell, :inline => "cd /root/dokku && make install-from-deb" end - config.vm.define "dokku-rpm", autostart: false do |vm| - vm.vm.box = "centos/7" - vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku" - vm.vm.network :forwarded_port, guest: 80, host: FORWARDED_PORT - vm.vm.hostname = "#{DOKKU_DOMAIN}" - vm.vm.network :private_network, ip: DOKKU_IP - vm.vm.provision :shell, :inline => "cd /root/dokku && bash bootstrap.sh" - end - config.vm.define "build", autostart: false do |vm| - vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku" - vm.vm.network :forwarded_port, guest: 80, host: FORWARDED_PORT + vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku", type: "nfs", nfs_version: 4, nfs_udp: false vm.vm.hostname = "#{DOKKU_DOMAIN}" vm.vm.network :private_network, ip: DOKKU_IP vm.vm.provision :shell, :inline => "export DEBIAN_FRONTEND=noninteractive && apt-get update -qq >/dev/null && apt-get -qq -y --no-install-recommends install git >/dev/null && cd /root/dokku && #{make_cmd}" - vm.vm.provision :shell, :inline => "export IS_RELEASE=true && cd /root/dokku && make deb-all rpm-all" + vm.vm.provision :shell, :inline => "export IS_RELEASE=true && cd /root/dokku && make deb-all" end config.vm.define "build-arch", autostart: false do |vm| vm.vm.box = "bugyt/archlinux" - vm.vm.synced_folder File.dirname(__FILE__), "/dokku" + vm.vm.synced_folder File.dirname(__FILE__), "/dokku", type: "nfs", nfs_version: 4, nfs_udp: false if Pathname.new("#{File.dirname(__FILE__)}/../dokku-arch").exist? - vm.vm.synced_folder "#{File.dirname(__FILE__)}/../dokku-arch", "/dokku-arch" + vm.vm.synced_folder "#{File.dirname(__FILE__)}/../dokku-arch", "/dokku-arch", type: "nfs", nfs_version: 4, nfs_udp: false end - vm.vm.network :forwarded_port, guest: 80, host: FORWARDED_PORT vm.vm.hostname = "#{DOKKU_DOMAIN}" vm.vm.network :private_network, ip: DOKKU_IP vm.vm.provision :shell, :inline => "cd /dokku && make arch-all", privileged: false diff --git a/bootstrap.sh b/bootstrap.sh index 9869aaa3bb5..b3653a7563a 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -3,7 +3,7 @@ set -eo pipefail [[ $TRACE ]] && set -x # A script to bootstrap dokku. -# It expects to be run on Ubuntu 18.04/20.04, or CentOS 7 via 'sudo' +# It expects to be run on Ubuntu 22.04/24.04 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. @@ -12,7 +12,7 @@ set -eo pipefail # 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 [9, 10, 11], CentOS [7], Fedora (partial) [33, 34], Ubuntu [18.04, 20.04]" +SUPPORTED_VERSIONS="Debian [11, 12, 13], Ubuntu [22.04, 24.04]" log-fail() { declare desc="log fail formatter" @@ -28,7 +28,7 @@ ensure-environment() { echo "Preparing to install $DOKKU_TAG from $DOKKU_REPO..." fi - hostname -f >/dev/null 2>&1 || { + 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" } @@ -48,9 +48,15 @@ install-requirements() { 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 + if ! command -v lsb_release &>/dev/null; then apt-get update -qq >/dev/null - apt-get -qq -y --no-install-recommends install software-properties-common + 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) @@ -63,7 +69,7 @@ install-requirements() { apt-get -qq -y --no-install-recommends install software-properties-common fi - add-apt-repository universe >/dev/null + add-apt-repository -y universe >/dev/null apt-get update -qq >/dev/null ;; esac @@ -122,7 +128,22 @@ install-dokku-from-source() { 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 software-properties-common + 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 @@ -139,9 +160,6 @@ install-dokku-from-package() { debian | ubuntu) install-dokku-from-deb-package "$@" ;; - centos | fedora | rhel) - install-dokku-from-rpm-package "$@" - ;; *) log-fail "Unsupported Linux distribution. For manual installation instructions, consult https://dokku.com/docs/getting-started/advanced-installation/" ;; @@ -163,7 +181,7 @@ install-dokku-from-deb-package() { local NO_INSTALL_RECOMMENDS=${DOKKU_NO_INSTALL_RECOMMENDS:=""} local OS_ID - if ! in-array "$DOKKU_DISTRO_VERSION" "18.04" "20.04" "9" "10" "11"; then + 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" fi @@ -171,10 +189,6 @@ install-dokku-from-deb-package() { NO_INSTALL_RECOMMENDS=" --no-install-recommends " fi - echo "--> Initial apt-get update" - apt-get update -qq >/dev/null - apt-get -qq -y --no-install-recommends install apt-transport-https - if ! command -v docker &>/dev/null; then echo "--> Installing docker" if uname -r | grep -q linode; then @@ -188,31 +202,31 @@ install-dokku-from-deb-package() { wget -nv -O - https://get.docker.com/ | sh fi - OS_ID="$(lsb_release -cs 2>/dev/null || echo "bionic")" + OS_ID="$(lsb_release -cs 2>/dev/null || echo "noble")" if ! in-array "$DOKKU_DISTRO" "debian" "ubuntu" "raspbian"; then DOKKU_DISTRO="ubuntu" - OS_ID="bionic" + OS_ID="noble" fi if [[ "$DOKKU_DISTRO" == "ubuntu" ]]; then - OS_IDS=("bionic" "focal") + OS_IDS=("jammy" "noble") if ! in-array "$OS_ID" "${OS_IDS[@]}"; then - OS_ID="bionic" + OS_ID="noble" fi elif [[ "$DOKKU_DISTRO" == "debian" ]]; then - OS_IDS=("stretch" "buster" "bullseye") + OS_IDS=("bullseye" "bookworm" "trixie") if ! in-array "$OS_ID" "${OS_IDS[@]}"; then - OS_ID="bullseye" + OS_ID="bookworm" fi elif [[ "$DOKKU_DISTRO" == "raspbian" ]]; then - OS_IDS=("buster") + OS_IDS=("bullseye" "bookworm" "trixie") if ! in-array "$OS_ID" "${OS_IDS[@]}"; then - OS_ID="buster" + OS_ID="bookworm" fi fi echo "--> Installing dokku" - wget -nv -O - https://packagecloud.io/dokku/dokku/gpgkey | apt-key add - + 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 @@ -223,48 +237,15 @@ install-dokku-from-deb-package() { [[ -n $DOKKU_NGINX_ENABLE ]] && echo "dokku dokku/nginx_enable string $DOKKU_NGINX_ENABLE" | sudo debconf-set-selections if [[ -n $DOKKU_CHECKOUT ]]; then - # shellcheck disable=SC2086 apt-get -qq -y $NO_INSTALL_RECOMMENDS install "dokku=$DOKKU_CHECKOUT" else - # shellcheck disable=SC2086 apt-get -qq -y $NO_INSTALL_RECOMMENDS install dokku fi } -install-dokku-from-rpm-package() { - local DOKKU_CHECKOUT="$1" - - if ! in-array "$DOKKU_DISTRO_VERSION" "7"; then - log-fail "Unsupported Linux distribution. Only the following versions are supported: $SUPPORTED_VERSIONS" - fi - - echo "--> Installing docker" - curl -fsSL https://get.docker.com/ | sh - - echo "--> Installing epel for nginx packages to be available" - yum install -y epel-release - - echo "--> Installing herokuish and dokku" - curl -s https://packagecloud.io/install/repositories/dokku/dokku/script.rpm.sh | bash - if [[ -n $DOKKU_CHECKOUT ]]; then - yum -y install herokuish "dokku-$DOKKU_CHECKOUT" - else - yum -y install herokuish dokku - fi - - echo "--> Enabling docker and nginx on system startup" - systemctl enable docker - systemctl enable nginx - - echo "--> Starting nginx" - systemctl start nginx -} - main() { export DOKKU_DISTRO DOKKU_DISTRO_VERSION - # shellcheck disable=SC1091 DOKKU_DISTRO=$(. /etc/os-release && echo "$ID") - # shellcheck disable=SC1091 DOKKU_DISTRO_VERSION=$(. /etc/os-release && echo "$VERSION_ID") export DEBIAN_FRONTEND=noninteractive diff --git a/common.mk b/common.mk index 4cc40f52433..0160634aaad 100644 --- a/common.mk +++ b/common.mk @@ -1,7 +1,7 @@ GO_ARGS ?= GO_PLUGIN_MAKE_TARGET ?= build GO_REPO_ROOT := /go/src/github.com/dokku/dokku -BUILD_IMAGE := golang:1.16.10 +BUILD_IMAGE := golang:1.26.2 GO_BUILD_CACHE ?= /tmp/dokku-go-build-cache GO_MOD_CACHE ?= /tmp/dokku-go-mod-mod GO_ROOT_MOUNT ?= $$PWD/../..:$(GO_REPO_ROOT) @@ -21,17 +21,17 @@ build-in-docker: clean -e GO111MODULE=on \ -w $(GO_REPO_ROOT)/plugins/$(PLUGIN_NAME) \ $(BUILD_IMAGE) \ - bash -c "GO_ARGS='$(GO_ARGS)' CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) make -j4 $(GO_PLUGIN_MAKE_TARGET)" || exit $$? + bash -c "GO_ARGS='$(GO_ARGS)' CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) GOWORK=off make -j4 $(GO_PLUGIN_MAKE_TARGET)" || exit $$? clean: rm -rf $(BUILD) find . -xtype l -delete commands: **/**/commands.go - go build -ldflags="-s -w" $(GO_ARGS) -o commands src/commands/commands.go + GOARCH=$(GOARCH) go build -mod=readonly -ldflags="-s -w" $(GO_ARGS) -o commands src/commands/commands.go subcommands: - go build -ldflags="-s -w" $(GO_ARGS) -o subcommands/subcommands src/subcommands/subcommands.go + GOARCH=$(GOARCH) go build -mod=readonly -ldflags="-s -w" $(GO_ARGS) -o subcommands/subcommands src/subcommands/subcommands.go $(MAKE) $(SUBCOMMANDS) subcommands/%: @@ -41,7 +41,7 @@ src-clean: rm -rf .gitignore src vendor Makefile *.go glide.* go.sum go.mod triggers: - go build -ldflags="-s -w" $(GO_ARGS) -o triggers src/triggers/triggers.go + GOARCH=$(GOARCH) go build -mod=readonly -ldflags="-s -w" $(GO_ARGS) -o triggers src/triggers/triggers.go $(MAKE) $(TRIGGERS) triggers/%: diff --git a/contrib/build-base.Dockerfile b/contrib/build-base.Dockerfile index f553a8cc96f..17259793fa9 100644 --- a/contrib/build-base.Dockerfile +++ b/contrib/build-base.Dockerfile @@ -1,6 +1,24 @@ -FROM ubuntu:18.04 +FROM ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update -qq && apt-get -qq -y --no-install-recommends install gcc git build-essential wget ruby-dev ruby1.9.1 lintian rpm help2man man-db -RUN command -v fpm >/dev/null || sudo gem install fpm --no-ri --no-rdoc +RUN apt-get update -qq && apt-get -qq -y --no-install-recommends install \ + adduser \ + build-essential \ + ca-certificates \ + coreutils \ + curl \ + gcc \ + git \ + help2man \ + jq \ + libc-bin \ + lintian \ + man-db \ + openssh-client \ + python3 \ + rpm \ + ruby-dev \ + sudo \ + wget +RUN command -v fpm || gem install fpm diff --git a/contrib/build-docs b/contrib/build-docs new file mode 100755 index 00000000000..db04310232f --- /dev/null +++ b/contrib/build-docs @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +set -eo pipefail + +readonly ROOT_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)" + +main() { + pushd "$ROOT_DIR" >/dev/null || return 1 + rm -rf docs-main tmp/docs-source tmp/docs-build # tmp/docs-build + cp -r docs docs-main + + echo "====> Generating docs image" + make docs-build-image + + echo "====> Fetching latest docs" + git clone -q https://github.com/dokku/docs.git tmp/docs-build + + echo "====> Processing version: latest" + rm -rf tmp/docs-build/docs + git clone -q https://github.com/dokku/dokku.git tmp/docs-source + + rm -rf docs/assets + mkdir -p docs/assets + cp -r docs-main/assets/extra.css docs/assets/extra.css + cp -r docs-main/assets/favicons docs/assets/favicons + cp -r docs-main/assets/dokku-logo.svg docs/assets/dokku-logo.svg + + rm -rf tmp/docs-build/assets + cp -r docs-main/assets tmp/docs-build/assets + cp docs-main/_build/app-nginx.conf.sigil tmp/docs-build/app-nginx.conf.sigil + mkdir -p tmp/docs-build/nginx.conf.d + touch tmp/docs-build/.static + echo "[]" >tmp/docs-build/versions.json + + sed -i.bak "s/outdated/outdated_hidden/g" docs/_overrides/main.html && rm docs/_overrides/main.html.bak + echo "----> Generating docs" + make docs-build + if [[ -n "$GITHUB_ACTION" ]]; then + sudo chown -R "$(whoami)" site + fi + + rm -rf site/_build + mv site tmp/docs-build/docs + mv docs/home.html tmp/docs-build/index.html + sed -i.bak "s/{{NAME}}~{{REF}}/docs/g" tmp/docs-build/index.html && rm tmp/docs-build/index.html.bak + sed -i.bak "s/{{NAME}}/docs/g" tmp/docs-build/index.html && rm tmp/docs-build/index.html.bak + sed -i.bak 's/="\/docs\/"/="\/"/g' tmp/docs-build/index.html && rm tmp/docs-build/index.html.bak + + jq -C -r '.["max-versions"][]' docs-main/assets/versions.json | while read -r current_version; do + major="$(echo "$current_version" | cut -d'.' -f1)" + minor="$(echo "$current_version" | cut -d'.' -f2)" + patch="$(echo "$current_version" | cut -d'.' -f3)" + while true; do + if [[ "$patch" -lt 0 ]]; then + break + fi + version="${major}.${minor}.${patch}" + echo "====> Processing version: ${major}.${minor}.${patch}" + + if [[ "$major" -lt 1 ]]; then + if [[ "$current_version" != "$version" ]]; then + echo " Writing rewrite rule" + echo "rewrite ^/docs~v$version/(.*)$ /docs~v$current_version/\$1 redirect;" >"tmp/docs-build/nginx.conf.d/$version.conf" + echo " Removing old docs" + rm -rf "tmp/docs-build/docs~v${version}" + patch=$((patch - 1)) + continue + fi + fi + + jq --arg title "v$version" --arg version "docs~v$version" '. += [{"title": $title, "version": $version, "aliases": []}]' tmp/docs-build/versions.json >tmp/docs-build/versions.json.bak + mv tmp/docs-build/versions.json.bak tmp/docs-build/versions.json + if [[ -d "tmp/docs-build/docs~v${version}" ]]; then + echo " ! Docs exist, skipping regeneration" + rm -rf "tmp/docs-build/docs~v${version}/assets/favicons" + cp -r docs-main/assets/favicons "tmp/docs-build/docs~v${version}/assets/favicons" + cp -r docs-main/assets/dokku-logo.svg "tmp/docs-build/docs~v${version}/assets/dokku-logo.svg" + cp -r docs-main/assets/extra.css "tmp/docs-build/docs~v${version}/assets/extra.css" + patch=$((patch - 1)) + continue + fi + + git -C tmp/docs-source checkout -q -- . + git -C tmp/docs-source checkout -q "tags/v${version}" -b "v${version}-branch" + rm -rf docs + mv tmp/docs-source/docs docs + + rm -rf docs/assets + mkdir -p docs/assets + cp -r docs-main/assets/favicons docs/assets/favicons + cp -r docs-main/assets/dokku-logo.svg docs/assets/dokku-logo.svg + cp -r docs-main/assets/extra.css docs/assets/extra.css + + cp -r docs-main/_build docs/_build + cp -r docs-main/_overrides docs/_overrides + git checkout -q -- mkdocs.yml + sed -i.bak "s/site_dir: site/site_dir: docs~v$version/g" mkdocs.yml && rm mkdocs.yml.bak + if [[ ! -f docs/template.html ]]; then + if [[ "$patch" -eq 0 ]]; then + break + fi + + continue + fi + + echo "----> Generating docs" + if ! make docs-build; then + continue + fi + + if [[ -d "docs~v${version}" ]]; then + if [[ -n "$GITHUB_ACTION" ]]; then + sudo chown -R "$(whoami)" "docs~v${version}" + fi + + rm -rf "docs~v${version}/_build" + mv "docs~v${version}" "tmp/docs-build/docs~v${version}" + fi + + if [[ "$patch" -eq 0 ]]; then + break + fi + patch=$((patch - 1)) + done + done + + echo "====> Rebuild versions" + if [[ -f "tmp/docs-build/versions.json" ]]; then + python3 contrib/sort-mkdocs-versions + else + echo " No version file exists" + fi + + echo "====> Cleanup main repo" + rm -rf docs + mv docs-main docs + git checkout -- mkdocs.yml docs + + echo "====> Checking for file changes" + changes=$(git -C tmp/docs-build ls-files -dmo) + if [[ -z "$changes" ]]; then + echo " ! No doc changes found" + exit 0 + fi + + if [[ "$changes" == "docs/sitemap.xml.gz" ]]; then + echo " ! No doc changes found" + git -C tmp/docs-build checkout -- docs/sitemap.xml.gz + exit 0 + fi + + echo "====> Changes found" + echo "$changes" + + echo "====> Committing changes" + git -C tmp/docs-build add . + git -C tmp/docs-build commit -m "chore: regenerate docs" + + echo "====> Pushing changes" + if [[ -n "$BOT_GITHUB_USERNAME" ]] && [[ -n "$BOT_GITHUB_API_TOKEN" ]]; then + git -C tmp/docs-build remote set-url origin "https://$BOT_GITHUB_USERNAME:$BOT_GITHUB_API_TOKEN@github.com/dokku/docs.git" + fi + + git -C tmp/docs-build push origin master + +} + +main "$@" diff --git a/contrib/build-dokku.Dockerfile b/contrib/build-dokku.Dockerfile index afadbb4bc85..7f08388fa6f 100644 --- a/contrib/build-dokku.Dockerfile +++ b/contrib/build-dokku.Dockerfile @@ -1,30 +1,29 @@ -FROM dokku/build-base:0.0.1 AS builder +FROM dokku/build-base:0.1.2 AS builder ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update -qq && apt-get -qq -y --no-install-recommends install gcc git build-essential wget ruby-dev ruby1.9.1 lintian rpm help2man man-db -RUN command -v fpm >/dev/null || sudo gem install fpm --no-ri --no-rdoc - ARG GOLANG_VERSION - -RUN wget -qO /tmp/go${GOLANG_VERSION}.linux-amd64.tar.gz https://storage.googleapis.com/golang/go${GOLANG_VERSION}.linux-amd64.tar.gz \ - && tar -C /usr/local -xzf /tmp/go${GOLANG_VERSION}.linux-amd64.tar.gz \ - && cp /usr/local/go/bin/* /usr/local/bin - ARG WORKDIR=/go/src/github.com/dokku/dokku WORKDIR ${WORKDIR} +RUN wget -qO /tmp/go${GOLANG_VERSION}.linux.tar.gz "https://go.dev/dl/go${GOLANG_VERSION}.linux-$(dpkg --print-architecture).tar.gz" \ + && tar -C /usr/local -xzf /tmp/go${GOLANG_VERSION}.linux.tar.gz \ + && cp /usr/local/go/bin/* /usr/local/bin \ + && mkdir -p ${WORKDIR}/contrib + COPY Makefile ${WORKDIR}/ COPY *.mk ${WORKDIR}/ +COPY contrib/dependencies.json ${WORKDIR}/contrib/dependencies.json -RUN make deb-setup rpm-setup sshcommand plugn +RUN make deb-setup sshcommand plugn COPY . ${WORKDIR} ENV GOPATH=/go +ENV GOROOT=/usr/local/go -FROM builder as amd64 +FROM builder AS amd64 ARG PLUGIN_MAKE_TARGET ARG DOKKU_VERSION=master @@ -37,9 +36,9 @@ RUN PLUGIN_MAKE_TARGET=${PLUGIN_MAKE_TARGET} \ IS_RELEASE=${IS_RELEASE} \ SKIP_GO_CLEAN=true \ make version copyfiles \ - && make deb-dokku rpm-dokku + && make deb-dokku -FROM builder as armhf +FROM builder AS arm64 COPY --from=amd64 /tmp /tmp COPY --from=amd64 /usr/local/share/man/man1/dokku.1 /usr/local/share/man/man1/dokku.1-generated @@ -56,7 +55,7 @@ RUN PLUGIN_MAKE_TARGET=${PLUGIN_MAKE_TARGET} \ DOKKU_GIT_REV=${DOKKU_GIT_REV} \ IS_RELEASE=${IS_RELEASE} \ SKIP_GO_CLEAN=true \ - GOARCH=arm make version copyfiles \ - && DOKKU_ARCHITECTURE=armhf GOARCH=arm make deb-dokku + GOARCH=arm64 make version copyfiles \ + && DOKKU_ARCHITECTURE=arm64 GOARCH=arm64 make deb-dokku RUN ls -lha /tmp/ diff --git a/contrib/copy-packages b/contrib/copy-packages index b2959ec9bbf..12458bf20b2 100644 --- a/contrib/copy-packages +++ b/contrib/copy-packages @@ -5,19 +5,17 @@ import shutil import subprocess from requests.auth import HTTPBasicAuth -PACKAGECLOUD_TOKEN = os.getenv('DOKKU_PACKAGECLOUD_TOKEN') +PACKAGECLOUD_TOKEN = os.getenv("DOKKU_PACKAGECLOUD_TOKEN") def download_file(filename, url): r = requests.get(url, stream=True) - with open(filename, 'wb') as f: + with open(filename, "wb") as f: shutil.copyfileobj(r.raw, f) def upload_file(filename): - versions = [ - "focal" - ] + versions = ["noble"] cmd_template = "package_cloud push dokku/dokku/ubuntu/{0} {1}" for version in versions: cmd = cmd_template.format(version, filename) @@ -25,21 +23,22 @@ def upload_file(filename): def main(): - auth = HTTPBasicAuth(PACKAGECLOUD_TOKEN, '') - base = requests.get('https://packagecloud.io/api/v1/repos/dokku/dokku/packages/deb/ubuntu/bionic.json', - auth=auth) + auth = HTTPBasicAuth(PACKAGECLOUD_TOKEN, "") + base = requests.get( + "https://packagecloud.io/api/v1/repos/dokku/dokku/packages/deb/ubuntu/jammy.json", + auth=auth, + ) data = base.json() urls = [] for package in data: - urls.append(package['versions_url']) + urls.append(package["versions_url"]) download_urls = {} for url in urls: - base = requests.get('https://packagecloud.io{0}'.format(url), - auth=auth) + base = requests.get("https://packagecloud.io{0}".format(url), auth=auth) data = base.json() for version in data: - download_urls['downloads/' + version['filename']] = version['download_url'] + download_urls["downloads/" + version["filename"]] = version["download_url"] for filename, download_url in download_urls.items(): print("downloading {0}".format(filename)) diff --git a/contrib/dependencies.json b/contrib/dependencies.json new file mode 100644 index 00000000000..e78b51a463a --- /dev/null +++ b/contrib/dependencies.json @@ -0,0 +1,104 @@ +{ + "dependencies": [ + { + "name": "docker-container-healthchecker", + "version": "0.15.2", + "urls": { + "amd64": "https://github.com/dokku/docker-container-healthchecker/releases/download/v0.15.2/docker-container-healthchecker-linux-amd64", + "arm64": "https://github.com/dokku/docker-container-healthchecker/releases/download/v0.15.2/docker-container-healthchecker-linux-arm64" + } + }, + { + "name": "docker-image-labeler", + "version": "0.9.0", + "urls": { + "amd64": "https://github.com/dokku/docker-image-labeler/releases/download/v0.9.0/docker-image-labeler-linux-amd64", + "arm64": "https://github.com/dokku/docker-image-labeler/releases/download/v0.9.0/docker-image-labeler-linux-arm64" + } + }, + { + "name": "lambda-builder", + "version": "0.9.3", + "urls": { + "amd64": "https://github.com/dokku/lambda-builder/releases/download/v0.9.3/lambda-builder-linux-amd64", + "arm64": "https://github.com/dokku/lambda-builder/releases/download/v0.9.3/lambda-builder-linux-arm64" + } + }, + { + "name": "netrc", + "version": "0.11.0", + "urls": { + "amd64": "https://github.com/dokku/netrc/releases/download/v0.11.0/netrc-linux-amd64", + "arm64": "https://github.com/dokku/netrc/releases/download/v0.11.0/netrc-linux-arm64" + } + }, + { + "name": "pack", + "version": "0.40.8", + "urls": { + "amd64": "https://github.com/buildpacks/pack/releases/download/v0.40.8/pack-v0.40.8-linux.tgz", + "arm64": "https://github.com/buildpacks/pack/releases/download/v0.40.8/pack-v0.40.8-linux-arm64.tgz" + } + }, + { + "name": "procfile-util", + "version": "0.20.7", + "urls": { + "amd64": "https://github.com/dokku/procfile-util/releases/download/v0.20.7/procfile-util-linux-amd64", + "arm64": "https://github.com/dokku/procfile-util/releases/download/v0.20.7/procfile-util-linux-arm64" + } + }, + { + "name": "sshcommand", + "version": "0.20.1", + "urls": { + "amd64": "https://github.com/dokku/sshcommand/releases/download/v0.20.1/sshcommand", + "arm64": "https://github.com/dokku/sshcommand/releases/download/v0.20.1/sshcommand" + } + } + ], + "predependencies": [ + { + "name": "gliderlabs-sigil", + "version": "0.12.0", + "urls": { + "amd64": "https://github.com/gliderlabs/sigil/releases/download/v0.12.0/sigil-linux-amd64", + "arm64": "https://github.com/gliderlabs/sigil/releases/download/v0.12.0/sigil-linux-arm64" + } + }, + { + "name": "plugn", + "version": "0.17.0", + "urls": { + "amd64": "https://github.com/dokku/plugn/releases/download/v0.17.0/plugn-linux-amd64", + "arm64": "https://github.com/dokku/plugn/releases/download/v0.17.0/plugn-linux-arm64" + } + } + ], + "recommendations": [ + { + "name": "dokku-event-listener", + "version": "0.20.0", + "urls": { + "amd64": "https://github.com/dokku/dokku-event-listener/releases/download/v0.20.0/dokku-event-listener-linux-amd64", + "arm64": "https://github.com/dokku/dokku-event-listener/releases/download/v0.20.0/dokku-event-listener-linux-arm64" + } + }, + { + "name": "dokku-update", + "version": "0.9.8", + "urls": { + "amd64": "https://github.com/dokku/dokku-update/releases/download/v0.9.8/dokku-update", + "arm64": "https://github.com/dokku/dokku-update/releases/download/v0.9.8/dokku-update" + } + }, + { + "name": "herokuish", + "version": "0.11.13", + "urls": { + "amd64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.13/herokuish_0.11.13_linux_x86_64.tgz", + "arm64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.13/herokuish_0.11.13_linux_x86_64.tgz" + } + } + ] +} diff --git a/contrib/dokku-docker-bin.sh b/contrib/dokku-docker-bin.sh index 3bc36232294..9619f465164 100755 --- a/contrib/dokku-docker-bin.sh +++ b/contrib/dokku-docker-bin.sh @@ -4,5 +4,4 @@ set -eo pipefail DOKKU_DOCKER_CONTAINER_NAME=${DOKKU_DOCKER_CONTAINER_NAME:=dokku} # TODO: handle cases where we need a tty -# shellcheck disable=SC2086 docker exec $DOKKU_DOCKER_ENV -i "$DOKKU_DOCKER_CONTAINER_NAME" dokku "$@" diff --git a/contrib/dokku_client.sh b/contrib/dokku_client.sh index 7db4416673f..2151568b62d 100755 --- a/contrib/dokku_client.sh +++ b/contrib/dokku_client.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash set -eo pipefail [[ $DOKKU_TRACE ]] && set -x -export DOKKU_PORT=${DOKKU_PORT:=22} +export DOKKU_PORT=${DOKKU_PORT:=} export DOKKU_HOST=${DOKKU_HOST:=} +export DOKKU_APP_PATH=${DOKKU_APP_PATH:=} fn-random-number() { [[ -n "$1" ]] && RANGE="$1" @@ -49,7 +50,7 @@ fn-dokku-host() { declare DOKKU_GIT_REMOTE="$1" DOKKU_HOST="$2" if [[ -z "$DOKKU_HOST" ]]; then - if [[ -d .git ]] || git rev-parse --git-dir >/dev/null 2>&1; then + if [[ -d .git ]] || git rev-parse --git-dir &>/dev/null; then DOKKU_HOST=$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | head -n 1 | cut -f1 -d' ' | cut -f2 -d '@' | cut -f1 -d':' 2>/dev/null || true) fi fi @@ -61,9 +62,43 @@ fn-dokku-host() { echo "$DOKKU_HOST" } +fn-dokku-app() { + declare DOKKU_GIT_REMOTE="$1" DOKKU_REMOTE_HOST="$2" + remote_output="$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | head -n 1 | awk '{print $2}')" + + # check if there is a scheme + if echo "$remote_output" | grep -qEi "ssh://"; then + echo "$remote_output" | grep -Ei "ssh://dokku@$DOKKU_REMOTE_HOST" | cut -f2 -d'@' | cut -f2 -d'/' 2>/dev/null + else + echo "$remote_output" | grep -Ei "dokku@$DOKKU_REMOTE_HOST" | cut -f2 -d'@' | cut -f2 -d':' 2>/dev/null + fi +} + +fn-dokku-port() { + declare DOKKU_GIT_REMOTE="$1" DOKKU_REMOTE_HOST="$2" + remote_output="$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | head -n 1 | awk '{print $2}')" + + if echo "$remote_output" | grep -qEi "ssh://"; then + local output="$(echo "$remote_output" | grep -Ei "ssh://dokku@$DOKKU_REMOTE_HOST" | cut -f2 -d'@' | cut -f1 -d'/' 2>/dev/null)" + # check if output has a : and if so, get the port + if echo "$output" | grep -qEi ":"; then + echo "$output" | cut -f2 -d':' 2>/dev/null + else + echo "22" + fi + else + echo "22" + fi +} + +fn-get-remote() { + git config dokku.remote 2>/dev/null || echo "dokku" +} + main() { declare CMD="$1" APP_ARG="$2" - local APP="" DOKKU_GIT_REMOTE="dokku" DOKKU_REMOTE_HOST="" + + local APP="" DOKKU_GIT_REMOTE="$(fn-get-remote)" DOKKU_REMOTE_HOST="" local cmd_set=false next_index=1 skip=false args=("$@") for arg in "$@"; do @@ -80,6 +115,10 @@ main() { APP=${args[$next_index]} skip=true shift 2 + elif [[ "$arg" == "--app-path" ]]; then + DOKKU_APP_PATH=${args[$next_index]} + skip=true + shift 2 elif [[ "$arg" == "--remote" ]]; then DOKKU_GIT_REMOTE=${args[$next_index]} skip=true @@ -99,22 +138,41 @@ main() { next_index=$((next_index + 1)) done + if [[ -n "$DOKKU_APP_PATH" ]]; then + pushd "$DOKKU_APP_PATH" &>/dev/null || exit 1 + trap 'popd &>/dev/null || true' RETURN INT TERM + fi + DOKKU_REMOTE_HOST="$(fn-dokku-host "$DOKKU_GIT_REMOTE" "$DOKKU_HOST")" - if [[ -z "$DOKKU_REMOTE_HOST" ]]; then + if [[ -z "$DOKKU_REMOTE_HOST" ]] && [[ "$CMD" != remote ]] && [[ "$CMD" != remote:* ]]; then fn-client-help-msg fi if [[ -z "$APP" ]]; then - if [[ -d .git ]] || git rev-parse --git-dir >/dev/null 2>&1; then + if [[ -d .git ]] || git rev-parse --git-dir &>/dev/null; then set +e - APP=$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | grep -Ei "dokku@$DOKKU_REMOTE_HOST" | head -n 1 | cut -f2 -d'@' | cut -f1 -d' ' | cut -f2 -d':' 2>/dev/null) + APP="$(fn-dokku-app "$DOKKU_GIT_REMOTE" "$DOKKU_REMOTE_HOST")" set -e else echo " ! This is not a git repository" 1>&2 fi fi + if [[ -z "$DOKKU_PORT" ]]; then + DOKKU_PORT="$(fn-dokku-port "$DOKKU_GIT_REMOTE" "$DOKKU_REMOTE_HOST")" + fi + case "$CMD" in + remote:show) + echo "dokku-app: $APP" + echo "dokku-git-remote: $DOKKU_GIT_REMOTE" + echo "dokku-host: $DOKKU_HOST" + echo "dokku-port: $DOKKU_PORT" + echo "dokku-remote-host: $DOKKU_REMOTE_HOST" + echo "dokku-ssh-user: dokku" + echo "dokku-constructed-remote: ssh://dokku@$DOKKU_REMOTE_HOST:$DOKKU_PORT/$APP" + exit 0 + ;; apps:create) if [[ -z "$APP" ]] && [[ -z "$APP_ARG" ]]; then APP=$(fn-random-name) @@ -125,7 +183,7 @@ main() { ssh -p "$DOKKU_PORT" "dokku@$DOKKU_REMOTE_HOST" apps exit 1 else - APP=$(random_name) + APP=$(fn-random-name) counter=$((counter + 1)) fi done @@ -143,16 +201,60 @@ main() { apps:destroy) fn-is-git-repo && fn-has-dokku-remote && git remote remove "$DOKKU_GIT_REMOTE" ;; + remote) + echo "$DOKKU_GIT_REMOTE" + exit 0 + ;; + remote:add) + shift 1 + git remote add "$@" + exit "$?" + ;; + remote:list) + git remote + exit "$?" + ;; + remote:set) + shift 1 + git config dokku.remote "$@" + exit "$?" + ;; + remote:remove) + shift 1 + git remote remove "$@" + local exit_code="$?" + if [[ "$(git config dokku.remote)" == "$1" ]]; then + git config --unset dokku.remote + fi + exit "$exit_code" + ;; + remote:unset) + if [[ -n "$(git config dokku.remote)" ]]; then + git config --unset dokku.remote + exit "$?" + fi + exit 0 + ;; esac [[ " apps certs help ls nginx shell storage trace version " == *" $CMD "* ]] && unset APP - [[ " certs:chain domains:add-global domains:remove-global domains:set-global ps:restore " == *" $CMD "* ]] && unset APP + [[ " certs:chain domains:add-global domains:clear-global domains:remove-global domains:set-global ps:restore " == *" $CMD "* ]] && unset APP + [[ " storage:ensure-directory " == *" $CMD "* ]] && unset APP + [[ " network:create network:destroy network:exists network:info network:list network:rebuildall " == *" $CMD "* ]] && unset APP + [[ " logs:vector-logs logs:vector-start logs:vector-stop " == *" $CMD "* ]] && unset APP + [[ " scheduler-k3s:cluster:add scheduler-k3s:cluster:list scheduler-k3s:cluster:remove " == *" $CMD "* ]] && unset APP + [[ " scheduler-k3s:ensure-charts scheduler-k3s:initialize scheduler-k3s:show-kubeconfig scheduler-k3s:uninstall " == *" $CMD "* ]] && unset APP + [[ " scheduler-k3s:profiles:add scheduler-k3s:profiles:list scheduler-k3s:profiles:remove " == *" $CMD "* ]] && unset APP [[ "$CMD" =~ events*|plugin*|ssh-keys* ]] && unset APP [[ -n "$APP_ARG" ]] && [[ "$APP_ARG" == "--global" ]] && unset APP - [[ -n "$@" ]] && [[ -n "$APP" ]] && app_arg="--app $APP" + if [[ -n "$@" ]] && [[ -n "$APP" ]]; then + set -- "$APP" "$@" + set -- "--app" "$@" + fi # echo "ssh -o LogLevel=QUIET -p $DOKKU_PORT -t dokku@$DOKKU_REMOTE_HOST -- $app_arg $@" - # shellcheck disable=SC2068,SC2086 - ssh -o LogLevel=QUIET -p $DOKKU_PORT -t dokku@$DOKKU_REMOTE_HOST -- $app_arg $@ || { + local ssh_args=("-o" "LogLevel=QUIET" "-p" "$DOKKU_PORT" "-t" "dokku@$DOKKU_REMOTE_HOST" "--") + ssh_args+=("$@") + ssh "${ssh_args[@]}" || { ssh_exit_code="$?" echo " ! Failed to execute dokku command over ssh: exit code $?" 1>&2 echo " ! If there was no output from Dokku, ensure your configured SSH Key can connect to the remote server" 1>&2 diff --git a/contrib/images/digitalocean/files/etc/nginx/sites-available/digitalocean b/contrib/images/digitalocean/files/etc/nginx/sites-available/digitalocean new file mode 100644 index 00000000000..9e9b34ce931 --- /dev/null +++ b/contrib/images/digitalocean/files/etc/nginx/sites-available/digitalocean @@ -0,0 +1,95 @@ +# You may add here your +# server { +# ... +# } +# statements for each of your virtual hosts to this file + +## +# You should look at the following URL's in order to grasp a solid understanding +# of Nginx configuration files in order to fully unleash the power of Nginx. +# http://wiki.nginx.org/Pitfalls +# http://wiki.nginx.org/QuickStart +# http://wiki.nginx.org/Configuration +# +# Generally, you will want to move this file somewhere, and start with a clean +# file but keep this around for reference. Or just disable in sites-enabled. +# +# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. +## + +server { + listen 80 default_server; + listen [::]:80 default_server ipv6only=on; + + root /var/www/html; + index index.php index.html index.htm; + + # Make site accessible from http://localhost/ + server_name localhost; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying a 404. + try_files $uri $uri/ =404; + # Uncomment to enable naxsi on this location + # include /etc/nginx/naxsi.rules + } + + error_page 404 /404.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + + location ~ \.php$ { + include snippets/fastcgi-php.conf; + fastcgi_pass unix:/run/php/php7.4-fpm.sock; + } + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} +} + + +# another virtual host using mix of IP-, name-, and port-based configuration +# +#server { +# listen 8000; +# listen somename:8080; +# server_name somename alias another.alias; +# root html; +# index index.html index.htm; +# +# location / { +# try_files $uri $uri/ =404; +# } +#} + + +# HTTPS server +# +#server { +# listen 443; +# server_name localhost; +# +# root html; +# index index.html index.htm; +# +# ssl on; +# ssl_certificate cert.pem; +# ssl_certificate_key cert.key; +# +# ssl_session_timeout 5m; +# +# ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2; +# ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; +# ssl_prefer_server_ciphers on; +# +# location / { +# try_files $uri $uri/ =404; +# } +#} diff --git a/contrib/images/digitalocean/files/etc/update-motd.d/99-one-click b/contrib/images/digitalocean/files/etc/update-motd.d/99-one-click new file mode 100755 index 00000000000..c4c8f37ddd6 --- /dev/null +++ b/contrib/images/digitalocean/files/etc/update-motd.d/99-one-click @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Configured as part of the DigitalOcean 1-Click Image build process + +myip=$(hostname -I | awk '{print$1}') +cat < >(tee /var/log/one_click_setup.log) 2>&1 + +ufw --force enable +printf "dokku dokku/hostname string %s" hostname | debconf-set-selections + +export DEBIAN_FRONTEND=noninteractive +export LANG=C +export LC_ALL=C + +# Install Dokku.... +for count in {1..30}; do + dpkg -i /var/lib/digitalocean/debs/*deb || /bin/true + apt-get -f install || /bin/true + + version=$(dpkg-query --showformat='${Version}' -W dokku) + if [[ -n "$version" ]]; then + ran=0 + exit 0 + else + count=$((count + 1)) + fi + sleep 5 +done diff --git a/contrib/images/digitalocean/files/var/lib/cloud/scripts/per-once/002_enable_ssh b/contrib/images/digitalocean/files/var/lib/cloud/scripts/per-once/002_enable_ssh new file mode 100755 index 00000000000..48bd764a51c --- /dev/null +++ b/contrib/images/digitalocean/files/var/lib/cloud/scripts/per-once/002_enable_ssh @@ -0,0 +1,9 @@ +#!/bin/bash +exec > >(tee /var/log/one_click_setup.log) 2>&1 + +# Remove the force command +sed -e '/Match user root/d' \ + -e '/.*ForceCommand.*droplet.*/d' \ + -i /etc/ssh/sshd_config + +systemctl restart ssh diff --git a/contrib/images/digitalocean/files/var/www/html/index.html b/contrib/images/digitalocean/files/var/www/html/index.html new file mode 100644 index 00000000000..989a2af6a96 --- /dev/null +++ b/contrib/images/digitalocean/files/var/www/html/index.html @@ -0,0 +1,169 @@ + + + + + + + +
+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Please log into your Droplet with SSH to configure the Dokku installation.

+

See the Dokku 1-Click Quickstart guide for detailed assistance.

+ Quickstart Guide +
+ + + diff --git a/contrib/images/digitalocean/in_parts/011-docker b/contrib/images/digitalocean/in_parts/011-docker new file mode 100755 index 00000000000..ef8da1f0a59 --- /dev/null +++ b/contrib/images/digitalocean/in_parts/011-docker @@ -0,0 +1,45 @@ +#!/bin/bash +set -eo pipefail +set -o errexit + +export DEBIAN_FRONTEND=noninteractive + +pkgs=(ca-certificates + curl + jq + linux-image-extra-virtual +) + +echo '--> Updating apt repositories' +apt-get -qqy update + +echo '--> Updating all packages' +apt-get -qqy -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' full-upgrade + +echo "--> Installing basic dependencies: ${pkgs[*]}" +apt-get -qqy install "${pkgs[@]}" + +echo '--> Setting up docker apt repository' +curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + +cat >/etc/apt/sources.list.d/docker.list < Installing docker-ce' +apt-get -y update +apt-get -y install docker-ce docker-buildx-plugin docker-compose-plugin + +echo '--> Enabling docker-ce' +systemctl enable docker +systemctl start docker + +echo '--> Enabling docker live-restore' +if [[ ! -f /etc/docker/daemon.json ]]; then + echo "{}" >/etc/docker/daemon.json +fi + +config="$(jq '. + {"live-restore": true}' /etc/docker/daemon.json)" +echo "$config" >/etc/docker/daemon.json +systemctl reload docker +docker info | grep -i live diff --git a/contrib/images/digitalocean/in_parts/011-ssh-message b/contrib/images/digitalocean/in_parts/011-ssh-message new file mode 100755 index 00000000000..83db94286d7 --- /dev/null +++ b/contrib/images/digitalocean/in_parts/011-ssh-message @@ -0,0 +1,10 @@ +#!/bin/bash +set -eo pipefail +set -o errexit + +echo '--> Prevent login until after setup is complete' +# Be a bit of a dork about login in +cat >>/etc/ssh/sshd_config < Install core dependencies for dokku' +apt-get -y install "${pkgs[@]}" + +echo '--> Setting up dokku apt repository' +curl -fsSL https://packagecloud.io/dokku/dokku/gpgkey -o /etc/apt/keyrings/dokku.asc + +cat >/etc/apt/sources.list.d/dokku.list < Install dependencies for dokku from new repository' +apt-get -y clean +apt-get -y update +apt-get -y install "${dokku_pkgs[@]}" +rm -f /etc/nginx/sites-enabled/default + +echo '--> Pre-cache dokku bits that are must run on boot' +apt-get -y clean +apt-get -y install --download-only "dokku=${DOKKU_VERSION}" dokku-event-listener herokuish + +mkdir -p /var/lib/digitalocean/debs +cp -au /var/cache/apt/archives/*deb /var/lib/digitalocean/debs diff --git a/contrib/images/digitalocean/in_parts/012-grub-opts b/contrib/images/digitalocean/in_parts/012-grub-opts new file mode 100755 index 00000000000..fbb043cb596 --- /dev/null +++ b/contrib/images/digitalocean/in_parts/012-grub-opts @@ -0,0 +1,9 @@ +#!/bin/bash +set -eo pipefail +set -o errexit + +echo '--> Update grub boot loader' +sed -e 's|GRUB_CMDLINE_LINUX="|GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1|g' \ + -i /etc/default/grub + +update-grub diff --git a/contrib/images/digitalocean/in_parts/014-docker-dns b/contrib/images/digitalocean/in_parts/014-docker-dns new file mode 100755 index 00000000000..e3b509edc49 --- /dev/null +++ b/contrib/images/digitalocean/in_parts/014-docker-dns @@ -0,0 +1,7 @@ +#!/bin/bash +set -eo pipefail +set -o errexit + +echo '--> Enable Google DNS for Docker' +sed -e 's|#DOCKER_OPTS|DOCKER_OPTS|g' \ + -i /etc/default/docker diff --git a/contrib/images/digitalocean/in_parts/014-ufw-rules b/contrib/images/digitalocean/in_parts/014-ufw-rules new file mode 100755 index 00000000000..957728b28eb --- /dev/null +++ b/contrib/images/digitalocean/in_parts/014-ufw-rules @@ -0,0 +1,12 @@ +#!/bin/bash +set -eo pipefail +set -o errexit + +echo '--> Enable and configure UFW' +sed -e 's|DEFAULT_FORWARD_POLICY=.*|DEFAULT_FORWARD_POLICY="ACCEPT"|g' \ + -i /etc/default/ufw + +ufw limit ssh +ufw allow 'Nginx Full' + +ufw --force enable diff --git a/contrib/images/digitalocean/in_parts/099-application_tag b/contrib/images/digitalocean/in_parts/099-application_tag new file mode 100755 index 00000000000..97a1f1617d0 --- /dev/null +++ b/contrib/images/digitalocean/in_parts/099-application_tag @@ -0,0 +1,21 @@ +#!/bin/bash +set -eo pipefail +set -o errexit + +echo '--> Write the application info' +application_version="$DOKKU_VERSION" +build_date="$(date -I)" +distro_arch="$(uname -m)" +distro_codename="$(lsb_release -sc)" +distro_release="$(lsb_release -sr)" +distro="$(lsb_release -si)" + +cat >/var/lib/digitalocean/application.info < Cleaning up tmp dir' +# Ensure /tmp exists and has the proper permissions before +# checking for security updates +# https://github.com/digitalocean/marketplace-partners/issues/94 +if [[ ! -d /tmp ]]; then + mkdir /tmp +fi +chmod 1777 /tmp +rm -rf /tmp/* /var/tmp/* + +echo '--> Cleaning up apt' +export DEBIAN_FRONTEND=noninteractive +apt-get -y update +apt-get -o Dpkg::Options::="--force-confold" upgrade -q -y --force-yes +apt-get -y autoremove +apt-get -y autoclean +apt-get -y purge droplet-agent + +echo '--> Clearing history' +history -c +cat /dev/null >/root/.bash_history +unset HISTFILE + +echo '--> Clearing log files' +find /var/log -mtime -1 -type f -exec truncate -s 0 {} \; +rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-???????? +cat /dev/null >/var/log/lastlog +cat /dev/null >/var/log/wtmp +rm -rf /var/lib/cloud/instances/* +rm -rf /tmp/* /var/tmp/* + +echo '--> Removing keys' +rm -f /root/.ssh/authorized_keys /etc/ssh/*key* +touch /etc/ssh/revoked_keys +chmod 600 /etc/ssh/revoked_keys + +echo '--> DO Directory contents' +ls -lah /opt/digitalocean || true + +echo '--> Securely erase the unused portion of the filesystem' +printf "\n\033[0;32mWriting zeros to the remaining disk space to securely +erase the unused portion of the file system. +Depending on your disk size this may take several minutes. +The secure erase will complete successfully when you see:\033[0m + dd: writing to '/zerofile': No space left on device\n +Beginning secure erase now\n" + +dd if=/dev/zero of=/zerofile bs=4096 || rm /zerofile diff --git a/contrib/images/digitalocean/in_parts/100-image-check b/contrib/images/digitalocean/in_parts/100-image-check new file mode 100755 index 00000000000..763020b6dc7 --- /dev/null +++ b/contrib/images/digitalocean/in_parts/100-image-check @@ -0,0 +1,631 @@ +#!/bin/bash + +# DigitalOcean Marketplace Image Validation Tool +# Š 2021-2022 DigitalOcean LLC. +# This code is licensed under Apache 2.0 license (see LICENSE.md for details) + +VERSION="v. 1.8.1" +RUNDATE=$(date) + +# Script should be run with SUDO +if [ "$EUID" -ne 0 ]; then + echo "[Error] - This script must be run with sudo or as the root user." + exit 1 +fi + +STATUS=0 +PASS=0 +WARN=0 +FAIL=0 + +# $1 == command to check for +# returns: 0 == true, 1 == false +cmdExists() { + if command -v "$1" >/dev/null 2>&1; then + return 0 + else + return 1 + fi +} + +function getDistro { + if [ -f /etc/os-release ]; then + # freedesktop.org and systemd + . /etc/os-release + OS=$NAME + VER=$VERSION_ID + elif type lsb_release >/dev/null 2>&1; then + # linuxbase.org + OS=$(lsb_release -si) + VER=$(lsb_release -sr) + elif [ -f /etc/lsb-release ]; then + # For some versions of Debian/Ubuntu without lsb_release command + . /etc/lsb-release + OS=$DISTRIB_ID + VER=$DISTRIB_RELEASE + elif [ -f /etc/debian_version ]; then + # Older Debian/Ubuntu/etc. + OS=Debian + VER=$(cat /etc/debian_version) + elif [ -f /etc/SuSe-release ]; then + # Older SuSE/etc. + : + elif [ -f /etc/redhat-release ]; then + # Older Red Hat, CentOS, etc. + VER=$(cut -d" " -f3 ", also works for BSD, etc. + OS=$(uname -s) + VER=$(uname -r) + fi +} +function loadPasswords { + SHADOW=$(cat /etc/shadow) +} + +function checkAgent { + # Check for the presence of the DO directory in the filesystem + if [ -d /opt/digitalocean ]; then + echo -en "\e[41m[FAIL]\e[0m DigitalOcean directory detected.\n" + ((FAIL++)) + STATUS=2 + if [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]]; then + echo "To uninstall the agent: 'sudo yum remove droplet-agent'" + echo "To remove the DO directory: 'find /opt/digitalocean/ -type d -empty -delete'" + elif [[ $OS == "Ubuntu" ]] || [[ $OS == "Debian" ]]; then + echo "To uninstall the agent and remove the DO directory: 'sudo apt-get purge droplet-agent'" + fi + else + echo -en "\e[32m[PASS]\e[0m DigitalOcean Monitoring agent was not found\n" + ((PASS++)) + fi +} + +function checkLogs { + cp_ignore="/var/log/cpanel-install.log" + echo -en "\nChecking for log files in /var/log\n\n" + # Check if there are log archives or log files that have not been recently cleared. + for f in /var/log/*-????????; do + [[ -e $f ]] || break + if [ "${f}" != "${cp_ignore}" ]; then + echo -en "\e[93m[WARN]\e[0m Log archive ${f} found; Contents:\n" + cat "${f}" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + done + for f in /var/log/*.[0-9]; do + [[ -e $f ]] || break + echo -en "\e[93m[WARN]\e[0m Log archive ${f} found; Contents:\n" + cat "${f}" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + done + for f in /var/log/*.log; do + [[ -e $f ]] || break + if [[ "${f}" = '/var/log/lfd.log' && "$(grep -E -v '/var/log/messages has been reset| Watching /var/log/messages' "${f}" | wc -c)" -gt 50 ]]; then + if [ "${f}" != "${cp_ignore}" ]; then + echo -en "\e[93m[WARN]\e[0m un-cleared log file, ${f} found; Contents:\n" + cat "${f}" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + elif [[ "${f}" != '/var/log/lfd.log' && "$(wc -c <"${f}")" -gt 50 ]]; then + if [ "${f}" != "${cp_ignore}" ]; then + echo -en "\e[93m[WARN]\e[0m un-cleared log file, ${f} found; Contents:\n" + cat "${f}" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + fi + done +} +function checkTMP { + # Check the /tmp directory to ensure it is empty. Warn on any files found. + if [[ -n "$(ls -A /tmp)" ]]; then + echo -en "\e[93m[WARN]\e[0m /tmp directory is not empty; Contents\n" + ls -A /tmp + ((WARN++)) + return 1 + fi + + echo -en "\e[32m[PASS]\e[0m /tmp directory is empty\n" + return 0 +} +function checkRoot { + user="root" + uhome="/root" + for usr in $SHADOW; do + IFS=':' read -r -a u <<<"$usr" + if [[ "${u[0]}" == "${user}" ]]; then + if [[ ${u[1]} == "!" ]] || [[ ${u[1]} == "!!" ]] || [[ ${u[1]} == "*" ]]; then + echo -en "\e[32m[PASS]\e[0m User ${user} has no password set.\n" + ((PASS++)) + else + echo -en "\e[41m[FAIL]\e[0m User ${user} has a password set on their account.\n" + ((FAIL++)) + STATUS=2 + fi + fi + done + if [ -d ${uhome}/ ]; then + if [ -d ${uhome}/.ssh/ ]; then + if ls ${uhome}/.ssh/* >/dev/null 2>&1; then + for key in "${uhome}"/.ssh/*; do + if [ "${key}" == "${uhome}/.ssh/authorized_keys" ]; then + + if [ "$(wc -c <"${key}")" -gt 50 ]; then + echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a populated authorized_keys file in \e[93m${key}\e[0m\n" + akey=$(cat "${key}") + echo "File Contents:" + echo "$akey" + echo "--------------" + ((FAIL++)) + STATUS=2 + fi + elif [ "${key}" == "${uhome}/.ssh/id_rsa" ]; then + if [ "$(wc -c <"${key}")" -gt 0 ]; then + echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a private key file in \e[93m${key}\e[0m\n" + akey=$(cat "${key}") + echo "File Contents:" + echo "$akey" + echo "--------------" + ((FAIL++)) + STATUS=2 + else + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has empty private key file in \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + elif [ "${key}" != "${uhome}/.ssh/known_hosts" ]; then + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a file in their .ssh directory at \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + else + if [ "$(wc -c <"${key}")" -gt 50 ]; then + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a populated known_hosts file in \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + fi + done + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m has no SSH keys present\n" + fi + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have an .ssh directory\n" + fi + if [ -f /root/.bash_history ]; then + + BH_S=$(wc -c = 1000 && $1 != "nobody" {print $1}' /dev/null 2>&1; then + for key in "${uhome}"/.ssh/*; do + if [ "${key}" == "${uhome}/.ssh/authorized_keys" ]; then + if [ "$(wc -c <"${key}")" -gt 50 ]; then + echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a populated authorized_keys file in \e[93m${key}\e[0m\n" + akey=$(cat "${key}") + echo "File Contents:" + echo "$akey" + echo "--------------" + ((FAIL++)) + STATUS=2 + fi + elif [ "${key}" == "${uhome}/.ssh/id_rsa" ]; then + if [ "$(wc -c <"${key}")" -gt 0 ]; then + echo -en "\e[41m[FAIL]\e[0m User \e[1m${user}\e[0m has a private key file in \e[93m${key}\e[0m\n" + akey=$(cat "${key}") + echo "File Contents:" + echo "$akey" + echo "--------------" + ((FAIL++)) + STATUS=2 + else + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has empty private key file in \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + elif [ "${key}" != "${uhome}/.ssh/known_hosts" ]; then + + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a file in their .ssh directory named \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + + else + if [ "$(wc -c <"${key}")" -gt 50 ]; then + echo -en "\e[93m[WARN]\e[0m User \e[1m${user}\e[0m has a known_hosts file in \e[93m${key}\e[0m\n" + ((WARN++)) + if [[ $STATUS != 2 ]]; then + STATUS=1 + fi + fi + fi + + done + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m has no SSH keys present\n" + fi + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have an .ssh directory\n" + fi + else + echo -en "\e[32m[ OK ]\e[0m User \e[1m${user}\e[0m does not have a directory in /home\n" + fi + + # Check for an uncleared .bash_history for this user + if [ -f "${uhome}/.bash_history" ]; then + BH_S=$(wc -c <"${uhome}/.bash_history") + + if [[ $BH_S -lt 200 ]]; then + echo -en "\e[32m[PASS]\e[0m ${user}'s Bash History appears to have been cleared\n" + ((PASS++)) + else + echo -en "\e[41m[FAIL]\e[0m ${user}'s Bash History should be cleared to prevent sensitive information from leaking\n" + ((FAIL++)) + STATUS=2 + + fi + echo -en "\n\n" + fi + fi + done +} +function checkFirewall { + + if [[ $OS == "Ubuntu" ]]; then + fw="ufw" + ufwa=$(ufw status | head -1 | sed -e "s/^Status:\ //") + if [[ $ufwa == "active" ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]]; then + if [ -f /usr/lib/systemd/system/csf.service ]; then + fw="csf" + if systemctl status $fw >/dev/null 2>&1; then + + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + elif cmdExists "firewall-cmd"; then + if [[ $(systemctl is-active firewalld >/dev/null 2>&1 && echo 1 || echo 0) ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + else + fw="firewalld" + if [[ $(systemctl is-active firewalld >/dev/null 2>&1 && echo 1 || echo 0) ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + fi + elif [[ "$OS" =~ Debian.* ]]; then + # user could be using a number of different services for managing their firewall + # we will check some of the most common + if cmdExists 'ufw'; then + fw="ufw" + ufwa=$(ufw status | head -1 | sed -e "s/^Status:\ //") + if [[ $ufwa == "active" ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + elif cmdExists "firewall-cmd"; then + fw="firewalld" + if [[ $(systemctl is-active --quiet $fw) ]]; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + else + # user could be using vanilla iptables, check if kernel module is loaded + fw="iptables" + if lsmod | grep -q '^ip_tables' 2>/dev/null; then + FW_VER="\e[32m[PASS]\e[0m Firewall service (${fw}) is active\n" + ((PASS++)) + else + FW_VER="\e[93m[WARN]\e[0m No firewall is configured. Ensure ${fw} is installed and configured\n" + ((WARN++)) + fi + fi + fi + +} +function checkUpdates { + if [[ $OS == "Ubuntu" ]] || [[ "$OS" =~ Debian.* ]]; then + # Ensure /tmp exists and has the proper permissions before + # checking for security updates + # https://github.com/digitalocean/marketplace-partners/issues/94 + if [[ ! -d /tmp ]]; then + mkdir /tmp + fi + chmod 1777 /tmp + + echo -en "\nUpdating apt package database to check for security updates, this may take a minute...\n\n" + apt-get -y update >/dev/null + + uc=$(apt-get --just-print upgrade | grep -i "security" -c) + if [[ $uc -gt 0 ]]; then + update_count=$((uc / 2)) + else + update_count=0 + fi + + if [[ $update_count -gt 0 ]]; then + echo -en "\e[41m[FAIL]\e[0m There are ${update_count} security updates available for this image that have not been installed.\n" + echo -en + echo -en "Here is a list of the security updates that are not installed:\n" + sleep 2 + apt-get --just-print upgrade | grep -i security | awk '{print $2}' | awk '!seen[$0]++' + echo -en + ((FAIL++)) + STATUS=2 + else + echo -en "\e[32m[PASS]\e[0m There are no pending security updates for this image.\n\n" + ((PASS++)) + fi + elif [[ $OS == "CentOS Linux" ]] || [[ $OS == "CentOS Stream" ]] || [[ $OS == "Rocky Linux" ]] || [[ $OS == "AlmaLinux" ]]; then + echo -en "\nChecking for available security updates, this may take a minute...\n\n" + + update_count=$(yum check-update --security --quiet | wc -l) + if [[ $update_count -gt 0 ]]; then + echo -en "\e[41m[FAIL]\e[0m There are ${update_count} security updates available for this image that have not been installed.\n" + ((FAIL++)) + STATUS=2 + else + echo -en "\e[32m[PASS]\e[0m There are no pending security updates for this image.\n" + ((PASS++)) + fi + else + echo "Error encountered" + exit 1 + fi + + return 1 +} +function checkCloudInit { + + if hash cloud-init 2>/dev/null; then + CI="\e[32m[PASS]\e[0m Cloud-init is installed.\n" + ((PASS++)) + else + CI="\e[41m[FAIL]\e[0m No valid verison of cloud-init was found.\n" + ((FAIL++)) + STATUS=2 + fi + return 1 +} + +clear +echo "DigitalOcean Marketplace Image Validation Tool ${VERSION}" +echo "Executed on: ${RUNDATE}" +echo "Checking local system for Marketplace compatibility..." + +getDistro + +echo -en "\n\e[1mDistribution:\e[0m ${OS}\n" +echo -en "\e[1mVersion:\e[0m ${VER}\n\n" + +ost=0 +osv=0 + +if [[ $OS == "Ubuntu" ]]; then + ost=1 + if [[ $VER == "24.04" ]] || [[ $VER == "22.10" ]] || [[ $VER == "22.04" ]] || [[ $VER == "20.04" ]] || [[ $VER == "18.04" ]] || [[ $VER == "16.04" ]]; then + osv=1 + fi + +elif [[ "$OS" =~ Debian.* ]]; then + ost=1 + case "$VER" in + 9) + osv=1 + ;; + 10) + osv=1 + ;; + 11) + osv=1 + ;; + 12) + osv=1 + ;; + *) + osv=2 + ;; + esac + +elif [[ $OS == "CentOS Linux" ]]; then + ost=1 + if [[ $VER == "8" ]]; then + osv=1 + elif [[ $VER == "7" ]]; then + osv=1 + elif [[ $VER == "6" ]]; then + osv=1 + else + osv=2 + fi +elif [[ $OS == "CentOS Stream" ]]; then + ost=1 + if [[ $VER == "8" ]]; then + osv=1 + else + osv=2 + fi +elif [[ $OS == "Rocky Linux" ]]; then + ost=1 + if [[ $VER =~ 8\. ]] || [[ $VER =~ 9\. ]]; then + osv=1 + else + osv=2 + fi +elif [[ $OS == "AlmaLinux" ]]; then + ost=1 + if [[ "$VERSION" =~ 8.* ]] || [[ "$VERSION" =~ 9.* ]]; then + osv=1 + else + osv=2 + fi +else + ost=0 +fi + +if [[ $ost == 1 ]]; then + echo -en "\e[32m[PASS]\e[0m Supported Operating System Detected: ${OS}\n" + ((PASS++)) +else + echo -en "\e[41m[FAIL]\e[0m ${OS} is not a supported Operating System\n" + ((FAIL++)) + STATUS=2 +fi + +if [[ $osv == 1 ]]; then + echo -en "\e[32m[PASS]\e[0m Supported Release Detected: ${VER}\n" + ((PASS++)) +elif [[ $ost == 1 ]]; then + echo -en "\e[41m[FAIL]\e[0m ${OS} ${VER} is not a supported Operating System Version\n" + ((FAIL++)) + STATUS=2 +else + echo "Exiting..." + exit 1 +fi + +checkCloudInit + +echo -en "${CI}" + +checkFirewall + +echo -en "${FW_VER}" + +checkUpdates + +loadPasswords + +checkLogs + +echo -en "\n\nChecking all user-created accounts...\n" +checkUsers + +echo -en "\n\nChecking the root account...\n" +checkRoot + +echo -en "\n\nChecking the /tmp directory...\n" +checkTMP + +checkAgent + +# Summary +echo -en "\n\n---------------------------------------------------------------------------------------------------\n" + +if [[ $STATUS == 0 ]]; then + echo -en "Scan Complete.\n\e[32mAll Tests Passed!\e[0m\n" +elif [[ $STATUS == 1 ]]; then + echo -en "Scan Complete. \n\e[93mSome non-critical tests failed. Please review these items.\e[0m\e[0m\n" +else + echo -en "Scan Complete. \n\e[41mOne or more tests failed. Please review these items and re-test.\e[0m\n" +fi +echo "---------------------------------------------------------------------------------------------------" +echo -en "\e[1m${PASS} Tests PASSED\e[0m\n" +echo -en "\e[1m${WARN} WARNINGS\e[0m\n" +echo -en "\e[1m${FAIL} Tests FAILED\e[0m\n" +echo -en "---------------------------------------------------------------------------------------------------\n" + +if [[ $STATUS == 0 ]]; then + echo -en "We did not detect any issues with this image. Please be sure to manually ensure that all software installed on the base system is functional, secure and properly configured (or facilities for configuration on first-boot have been created).\n\n" + exit 0 +elif [[ $STATUS == 1 ]]; then + echo -en "Please review all [WARN] items above and ensure they are intended or resolved. If you do not have a specific requirement, we recommend resolving these items before image submission\n\n" + exit 0 +else + echo -en "Some critical tests failed. These items must be resolved and this scan re-run before you submit your image to the DigitalOcean Marketplace.\n\n" + exit 1 +fi diff --git a/contrib/images/digitalocean/packer.pkr.hcl b/contrib/images/digitalocean/packer.pkr.hcl new file mode 100644 index 00000000000..40076b1f613 --- /dev/null +++ b/contrib/images/digitalocean/packer.pkr.hcl @@ -0,0 +1,71 @@ +packer { + required_plugins { + digitalocean = { + version = ">= 1.0.4" + source = "github.com/digitalocean/digitalocean" + } + } +} + +variable "dokku_version" { + type = string +} + +source "digitalocean" "ubuntu" { + image = "ubuntu-24-04-x64" + region = "nyc1" + size = "s-1vcpu-512mb-10gb" + ssh_username = "root" + snapshot_name = "dokku-${var.dokku_version}-snapshot-{{timestamp}}" +} + +build { + name = "dokku" + sources = [ + "source.digitalocean.ubuntu" + ] + + provisioner "shell" { + inline = [ + "echo '--> Waiting until cloud-init is complete'", + "/usr/bin/cloud-init status --wait", + ] + valid_exit_codes = [0, 2] + } + + provisioner "file" { + source = "${path.root}/files/etc/" + destination = "/etc/" + } + + provisioner "file" { + source = "${path.root}/files/var/" + destination = "/var/" + } + + provisioner "shell" { + environment_vars = [ + "DOKKU_VERSION=${var.dokku_version}", + "DEBIAN_FRONTEND=noninteractive", + "LC_ALL=C", + "LANG=en_US.UTF-8", + "LC_CTYPE=en_US.UTF-8", + ] + + scripts = [ + "${path.root}/in_parts/011-docker", + "${path.root}/in_parts/011-ssh-message", + "${path.root}/in_parts/012-dokku-packages", + "${path.root}/in_parts/012-grub-opts", + "${path.root}/in_parts/014-docker-dns", + "${path.root}/in_parts/014-ufw-rules", + "${path.root}/in_parts/099-application_tag", + "${path.root}/in_parts/099-cleanup", + "${path.root}/in_parts/100-image-check", + ] + } + + post-processor "manifest" { + output = "digitalocean-manifest.json" + } +} diff --git a/contrib/packer.json b/contrib/packer.json deleted file mode 100644 index 96a60e5b2f4..00000000000 --- a/contrib/packer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "builders": [ - { - "type": "digitalocean", - "region": "nyc3", - "image": "ubuntu-14-04-x64" - } - ], - "provisioners": [ - { - "type": "file", - "source": "deb.mk", - "destination": "/tmp/Makefile" - }, - { - "type": "shell", - "inline": [ - "echo '--> Waiting 30 seconds for core services to be available'", - "sleep 30", - - "echo '--> Updating apt repositories'", - "sudo apt-get update -qq >/dev/null", - - "echo '--> Installing make requirement'", - "sudo apt-get -qq -y --no-install-recommends install build-essential", - - "cd /tmp && make install-from-deb", - "rm /root/.ssh/authorized_keys" - ] - } - ] -} diff --git a/contrib/release-dokku b/contrib/release-dokku index faa929f249a..3f69153e729 100755 --- a/contrib/release-dokku +++ b/contrib/release-dokku @@ -9,19 +9,16 @@ readonly DOKKU_GIT_REV="$(git rev-parse HEAD)" trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT log-info() { - # shellcheck disable=SC2034 declare desc="Log info formatter" echo "$*" } log-error() { - # shellcheck disable=SC2034 declare desc="Log error formatter" echo "! $*" 1>&2 } log-fail() { - # shellcheck disable=SC2034 declare desc="Log fail formatter" log-error "$*" exit 1 @@ -134,11 +131,10 @@ fn-publish-package() { local OS_ID ex [[ "$IS_RELEASE" == "true" ]] && REPOSITORY=dokku/dokku - [[ "$RELEASE_TYPE" == "rpm" ]] && DIST=el/7 [[ "$RELEASE_TYPE" == "raspbian" ]] && DIST=raspbian if [[ "$RELEASE_TYPE" == "raspbian" ]]; then - OS_IDS=("buster") + OS_IDS=("bullseye" "bookworm" "trixie") for OS_ID in "${OS_IDS[@]}"; do log-info "(release-dokku) pushing deb to packagecloud.com/${REPOSITORY}/${DIST}" package_cloud push "${REPOSITORY}/${DIST}/${OS_ID}" "$PACKAGE_NAME" @@ -148,7 +144,7 @@ fn-publish-package() { fi done elif [[ "$DIST" == "ubuntu" ]]; then - OS_IDS=("bionic" "focal") + OS_IDS=("jammy" "noble") 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" @@ -159,7 +155,7 @@ fn-publish-package() { done DIST=debian - OS_IDS=("stretch" "buster" "bullseye") + OS_IDS=("bullseye" "bookworm" "trixie") 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" @@ -187,8 +183,12 @@ fn-replace-version() { fn-repo-merged-requests() { declare desc="Retrieves all pull request ids since a given release of dokku" - declare TAG="$1" - git log "v${TAG}..HEAD" --oneline | grep "Merge pull request" | cut -d' ' -f 5 | cut -d '#' -f2 + declare CURRENT_VERSION="$1" NEXT_VERSION="$2" IS_PATCH="$3" + if [[ "$IS_PATCH" == "true" ]]; then + git log "v${CURRENT_VERSION}..HEAD" --oneline | grep "Merge pull request" | cut -d' ' -f 5 | cut -d '#' -f2 + else + curl -u "$RELEASE_GITHUB_USERNAME:$RELEASE_GITHUB_API_TOKEN" --fail -sS "https://api.github.com/search/issues?q=repo:dokku/dokku+milestone:v${NEXT_VERSION}+is:pr&per_page=100" | jq '.items[].number' + fi } fn-repo-push-tags() { @@ -205,7 +205,7 @@ fn-repo-push-tags() { fn-repo-update() { declare desc="Updates files within the repository for a given release" - declare IS_RELEASE="$1" CURRENT_VERSION="$2" NEXT_VERSION="$3" + declare IS_RELEASE="$1" CURRENT_VERSION="$2" NEXT_VERSION="$3" IS_PATCH="$4" log-info "(release-dokku) Replacing ${CURRENT_VERSION} with ${NEXT_VERSION}" for f in plugins/*/plugin.toml; do @@ -224,15 +224,15 @@ fn-repo-update() { fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/style.css fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/development/release-process.md fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/getting-started/install/docker.md - fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/getting-started/installation.md - fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/home.html + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/getting-started/installation/index.md + fn-replace-version "v$CURRENT_VERSION" "v$NEXT_VERSION" docs/home.html fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/template.html fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" README.md if [[ "$RELEASE" == 'patch' ]]; then fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/versions.json else - versions=$(python -c 'import json,sys;d=json.load(sys.stdin);d["max-versions"].append("'"${NEXT_VERSION}"'"); print(json.dumps(d, indent=2, sort_keys=True))' docs/assets/versions.json git add docs/assets/versions.json fi @@ -242,24 +242,34 @@ fn-repo-update() { fi log-info "(release-dokku) Adding HISTORY.md, committing and tagging" - fn-repo-update-history-and-commit "$CURRENT_VERSION" "$NEXT_VERSION" + fn-repo-update-history-and-commit "$CURRENT_VERSION" "$NEXT_VERSION" "$IS_PATCH" git tag "v${NEXT_VERSION}" } fn-repo-update-history-and-commit() { declare desc="Updates the history file and commits the changes" - declare CURRENT_VERSION="$1" NEXT_VERSION="$2" + declare CURRENT_VERSION="$1" NEXT_VERSION="$2" IS_PATCH="$3" local PULL_REQUEST_ID TITLE AUTHOR TYPE CHANGELOG_TEXT - local COMMIT_MESSAGE HISTORY HISTORY_CONTENTS HISTORY_DOCUMENTATION HISTORY_ENHANCEMENT HISTORY_BC_BREAK HISTORY_REFACTOR HISTORY_BUG HISTORY_OTHER HISTORY_TESTS ISSUE_FILE + local COMMIT_MESSAGE HISTORY HISTORY_CONTENTS HISTORY_BUG HISTORY_DEPENDENCY HISTORY_DEPRECATION HISTORY_DOCUMENTATION HISTORY_ENHANCEMENT HISTORY_BC_BREAK HISTORY_REFACTOR HISTORY_REMOVAL HISTORY_SECURITY HISTORY_OTHER HISTORY_TESTS ISSUE_FILE pushd "$ROOT_DIR" >/dev/null mkdir -p /tmp/github-data - for PULL_REQUEST_ID in $(fn-repo-merged-requests "$CURRENT_VERSION"); do + for PULL_REQUEST_ID in $(fn-repo-merged-requests "$CURRENT_VERSION" "$NEXT_VERSION" "$IS_PATCH"); do ISSUE_FILE="/tmp/github-data/${PULL_REQUEST_ID}.json" if [[ ! -f "$ISSUE_FILE" ]]; then - curl -sS "https://api.github.com/repos/dokku/dokku/issues/${PULL_REQUEST_ID}" >"$ISSUE_FILE" + while true; do + if OUTPUT=$(curl -u "$RELEASE_GITHUB_USERNAME:$RELEASE_GITHUB_API_TOKEN" --fail -sS "https://api.github.com/repos/dokku/dokku/issues/${PULL_REQUEST_ID}" 2>&1); then + echo "$OUTPUT" >"$ISSUE_FILE" + break + fi + + log-error "Error while fetching info for pull/$PULL_REQUEST_ID, sleeping for 5 seconds" + log-error "$OUTPUT" + sleep 5 + log-info " Retrying pull/$PULL_REQUEST_ID" + done fi TITLE="$(jq -r '.title' "$ISSUE_FILE")" @@ -267,16 +277,26 @@ fn-repo-update-history-and-commit() { TYPE="$(jq -r '.labels[] | select( .name | contains("type") ) | .name' "$ISSUE_FILE" | cut -d' ' -f2)" CHANGELOG_TEXT="- #${PULL_REQUEST_ID}: @${AUTHOR} ${TITLE}" - if [[ "$TYPE" == "bc-break" ]]; then + if [[ "$TYPE" == "release" ]]; then + continue + elif [[ "$TYPE" == "bc-break" ]]; then HISTORY_BC_BREAK="${HISTORY_BC_BREAK}"$'\n'"$CHANGELOG_TEXT" elif [[ "$TYPE" == "bug" ]]; then HISTORY_BUG="${HISTORY_BUG}"$'\n'"$CHANGELOG_TEXT" + elif [[ "$TYPE" == "dependencies" ]]; then + HISTORY_DEPENDENCY="${HISTORY_DEPENDENCY}"$'\n'"$CHANGELOG_TEXT" + elif [[ "$TYPE" == "deprecation" ]]; then + HISTORY_DEPRECATION="${HISTORY_DEPRECATION}"$'\n'"$CHANGELOG_TEXT" elif [[ "$TYPE" == "documentation" ]]; then HISTORY_DOCUMENTATION="${HISTORY_DOCUMENTATION}"$'\n'"$CHANGELOG_TEXT" elif [[ "$TYPE" == "enhancement" ]]; then HISTORY_ENHANCEMENT="${HISTORY_ENHANCEMENT}"$'\n'"$CHANGELOG_TEXT" elif [[ "$TYPE" == "refactor" ]]; then HISTORY_REFACTOR="${HISTORY_REFACTOR}"$'\n'"$CHANGELOG_TEXT" + elif [[ "$TYPE" == "removal" ]]; then + HISTORY_REMOVAL="${HISTORY_REMOVAL}"$'\n'"$CHANGELOG_TEXT" + elif [[ "$TYPE" == "security" ]]; then + HISTORY_SECURITY="${HISTORY_SECURITY}"$'\n'"$CHANGELOG_TEXT" elif [[ "$TYPE" == "tests" ]]; then HISTORY_TESTS="${HISTORY_TESTS}"$'\n'"$CHANGELOG_TEXT" else @@ -288,7 +308,7 @@ fn-repo-update-history-and-commit() { HISTORY="${HISTORY}"$'\n\n'"## ${NEXT_VERSION}" HISTORY="${HISTORY}"$'\n\n'"Install/update via the bootstrap script:" HISTORY="${HISTORY}"$'\n\n'"\`\`\`shell" - HISTORY="${HISTORY}"$'\n'"wget https://raw.githubusercontent.com/dokku/dokku/v${NEXT_VERSION}/bootstrap.sh" + HISTORY="${HISTORY}"$'\n'"wget -NP . https://dokku.com/install/v${NEXT_VERSION}/bootstrap.sh" HISTORY="${HISTORY}"$'\n'"sudo DOKKU_TAG=v${NEXT_VERSION} bash bootstrap.sh" HISTORY="${HISTORY}"$'\n'"\`\`\`" @@ -296,6 +316,11 @@ fn-repo-update-history-and-commit() { HISTORY="${HISTORY}"$'\n\n'"See the [${NEXT_VERSION} migration guide](/docs/appendices/${NEXT_VERSION}-migration-guide.md) for more information on migrating to ${NEXT_VERSION}." fi + if [[ "$HISTORY_SECURITY" ]]; then + HISTORY="${HISTORY}"$'\n\n'"### Security" + HISTORY="${HISTORY}"$'\n'"$HISTORY_SECURITY" + fi + if [[ "$HISTORY_BC_BREAK" ]]; then HISTORY="${HISTORY}"$'\n\n'"### Backwards Compatibility Breaks" HISTORY="${HISTORY}"$'\n'"$HISTORY_BC_BREAK" @@ -311,6 +336,16 @@ fn-repo-update-history-and-commit() { HISTORY="${HISTORY}"$'\n'"$HISTORY_ENHANCEMENT" fi + if [[ "$HISTORY_REMOVAL" ]]; then + HISTORY="${HISTORY}"$'\n\n'"### Removals" + HISTORY="${HISTORY}"$'\n'"$HISTORY_REMOVAL" + fi + + if [[ "$HISTORY_DEPRECATION" ]]; then + HISTORY="${HISTORY}"$'\n\n'"### Deprecations" + HISTORY="${HISTORY}"$'\n'"$HISTORY_DEPRECATION" + fi + if [[ "$HISTORY_REFACTOR" ]]; then HISTORY="${HISTORY}"$'\n\n'"### Refactors" HISTORY="${HISTORY}"$'\n'"$HISTORY_REFACTOR" @@ -326,6 +361,11 @@ fn-repo-update-history-and-commit() { HISTORY="${HISTORY}"$'\n'"$HISTORY_TESTS" fi + if [[ "$HISTORY_DEPENDENCY" ]]; then + HISTORY="${HISTORY}"$'\n\n'"### Dependencies" + HISTORY="${HISTORY}"$'\n'"$HISTORY_DEPENDENCY" + fi + if [[ "$HISTORY_OTHER" ]]; then HISTORY="${HISTORY}"$'\n\n'"### Other" HISTORY="${HISTORY}"$'\n'"$HISTORY_OTHER" @@ -350,13 +390,29 @@ fn-require-bin() { fn-build-docker-image() { declare desc="Builds the dokku docker image and tags it with the given version" - declare VERSION="$1" - docker build -t "dokku/dokku:$VERSION" . + declare VERSION="$1" IS_RELEASE="$2" + + if [[ "$IS_RELEASE" == "true" ]]; then + docker buildx build \ + --platform linux/arm64,linux/amd64 \ + --progress plain \ + --push \ + --label "org.opencontainers.image.version=$VERSION" \ + --tag "dokku/dokku:latest" \ + --tag "dokku/dokku:$VERSION" . + elif [[ "$SKIP_IMAGE_BUILD" != "true" ]]; then + docker buildx build \ + --platform linux/arm64,linux/amd64 \ + --progress plain \ + --label "org.opencontainers.image.version=$VERSION" \ + --tag "dokku/dokku:latest" \ + --tag "dokku/dokku:$(echo "$VERSION" | tr + -)" . + fi } main() { declare RELEASE="$1" - local CURRENT_VERSION NEXT_VERSION IS_RELEASE + local CURRENT_VERSION NEXT_VERSION IS_PATCH IS_RELEASE local VALID_RELEASE_LEVELS=("major" "minor" "patch" "betafish" "build") if [[ "$RELEASE" == '--trace' ]]; then @@ -387,36 +443,35 @@ main() { NEXT_VERSION="$(fn-version-next "$CURRENT_VERSION" "$RELEASE")" IS_RELEASE="$(fn-is-release "$RELEASE")" + IS_PATCH="false" + if [[ "$RELEASE" == "patch" ]]; then + IS_PATCH="true" + fi + echo "v${NEXT_VERSION}" >build/next-version - fn-repo-update "$IS_RELEASE" "$CURRENT_VERSION" "$NEXT_VERSION" + fn-repo-update "$IS_RELEASE" "$CURRENT_VERSION" "$NEXT_VERSION" "$IS_PATCH" fn-build-dokku "$IS_RELEASE" "$NEXT_VERSION" || log-fail "Error building package" - fn-extract-package "$IS_RELEASE" "dokku_${NEXT_VERSION}_armhf.deb" || log-fail "Error extracting deb package" + fn-extract-package "$IS_RELEASE" "dokku_${NEXT_VERSION}_arm64.deb" || log-fail "Error extracting deb package" fn-extract-package "$IS_RELEASE" "dokku_${NEXT_VERSION}_amd64.deb" || log-fail "Error extracting deb package" - fn-extract-package "$IS_RELEASE" "dokku-${NEXT_VERSION}-1.x86_64.rpm" || log-fail "Error extracting rpm package" - cp -f "build/dokku_${NEXT_VERSION}_amd64.deb" build/dokku.deb + mkdir -p build/package + cp -f "build/dokku_${NEXT_VERSION}_amd64.deb" build/package/dokku-amd64.deb + cp -f "build/dokku_${NEXT_VERSION}_arm64.deb" build/package/dokku-arm64.deb if [[ "$RELEASE" != "build" ]]; then - fn-publish-package "$IS_RELEASE" "raspbian" "build/dokku_${NEXT_VERSION}_armhf.deb" || log-fail "Error publishing deb package" + fn-publish-package "$IS_RELEASE" "deb" "build/dokku_${NEXT_VERSION}_arm64.deb" || log-fail "Error publishing deb package" fn-publish-package "$IS_RELEASE" "deb" "build/dokku_${NEXT_VERSION}_amd64.deb" || log-fail "Error publishing deb package" - fn-publish-package "$IS_RELEASE" "rpm" "build/dokku-${NEXT_VERSION}-1.x86_64.rpm" || log-fail "Error publishing rpm package" - fn-build-docker-image "$NEXT_VERSION" || log-fail "Error building docker image" + fn-build-docker-image "$NEXT_VERSION" "$IS_RELEASE" || log-fail "Error building docker image" - if [[ "$IS_RELEASE" == "true" ]]; then - docker tag "dokku/dokku:$NEXT_VERSION" "dokku/dokku:latest" - docker push "dokku/dokku:$NEXT_VERSION" - docker push "dokku/dokku:latest" - fi fn-repo-push-tags "$IS_RELEASE" + else + fn-build-docker-image "$NEXT_VERSION" "$IS_RELEASE" || log-fail "Error building docker image" fi if [[ "$IS_RELEASE" != "true" ]]; then git reset -q HEAD plugins/*/plugin.toml git checkout -- plugins/*/plugin.toml fi - - echo "build/dokku_${NEXT_VERSION}_amd64.deb" >build/deb-filename - echo "build/dokku-${NEXT_VERSION}-1.x86_64.rpm" >build/rpm-filename } main "$@" diff --git a/contrib/release-plugin b/contrib/release-plugin index e0cbc55f20d..f034414b2c4 100755 --- a/contrib/release-plugin +++ b/contrib/release-plugin @@ -7,19 +7,16 @@ readonly TMP_WORK_DIR="$(mktemp -d "/tmp/dokku-plugin-release.XXXXXX")" trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT log-info() { - # shellcheck disable=SC2034 declare desc="Log info formatter" echo "$*" } log-error() { - # shellcheck disable=SC2034 declare desc="Log error formatter" echo "! $*" 1>&2 } log-fail() { - # shellcheck disable=SC2034 declare desc="Log fail formatter" log-error "$*" exit 1 diff --git a/contrib/sort-mkdocs-versions b/contrib/sort-mkdocs-versions new file mode 100644 index 00000000000..21ef2002058 --- /dev/null +++ b/contrib/sort-mkdocs-versions @@ -0,0 +1,38 @@ +#!/usr/bin/env python +import json + + +def main(): + version_file = "tmp/docs-build/versions.json" + data = [] + with open(version_file) as f: + data = json.load(f) + + versions = [] + for info in data: + if info["title"] == "latest": + continue + versions.append(info["title"].removeprefix("v")) + + versions.sort(key=lambda x: list(map(int, x.split('.'))), reverse=True) + data = [ + { + "aliases": ["latest"], + "title": "latest", + "version": "docs", + } + ] + + for version in versions: + data.append({ + "aliases": [], + "title": f"v{version}", + "version": f"docs~v{version}", + }) + + with open(version_file, "w") as f: + json.dump(data, f, indent=4) + + +if __name__ == "__main__": + main() diff --git a/contrib/update-deb-dependencies b/contrib/update-deb-dependencies new file mode 100755 index 00000000000..84be4ccf6ec --- /dev/null +++ b/contrib/update-deb-dependencies @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +import json +import re + + +def main(): + """ + updates the control file with the tested debian dependencies + """ + deps = {} + with open("contrib/dependencies.json", encoding="utf-8") as handle: + deps = json.load(handle) + + control_lines = [] + with open("debian/control", encoding="utf-8") as handle: + control_lines = [line for line in handle.readlines()] + + for key in ["dependencies", "predependencies", "recommendations"]: + for dependency in deps[key]: + name = dependency["name"] + version = dependency["version"] + pattern = re.compile(rf"(?= {version})", line) + + with open("debian/control", mode="w", encoding="utf-8") as handle: + handle.writelines(control_lines) + + +if __name__ == "__main__": + main() diff --git a/contrib/write-mkdocs b/contrib/write-mkdocs new file mode 100644 index 00000000000..50fbf98c6f1 --- /dev/null +++ b/contrib/write-mkdocs @@ -0,0 +1,147 @@ +#!/usr/bin/env python +import os +import pathlib +import shutil +import sys + +import yaml +from bs4 import BeautifulSoup + + +def get_nav_from_selector(soup: BeautifulSoup, selector: str, docs_dir: str): + """ + Generates navigation from template file + """ + navigation = [] + header_name = "" + for anchor in soup.select(selector): + classes = anchor.get("class", []) + if classes is None: + continue + + if "disabled" in classes: + header_name = anchor.text + navigation.append({header_name: []}) + + href = anchor.get("href", "") + if href is None: + continue + + href = str(href) + if href == "#": + continue + + url = href.replace("{{NAME}}", "") + filename = url.replace("http://progrium.viewdocs.io/dokku/", "") + filename = filename.replace("http://dokku.viewdocs.io/dokku/", "") + filename = filename.strip("/") + ".md" + filename = filename.removeprefix("dokku/") + + ignore_errors_for = [ + "getting-started/installation.md", + "getting-started/upgrading.md", + ] + if not os.path.exists(docs_dir + "/" + filename): + if filename not in ignore_errors_for: + print("error fetching markdown file:", filename) + continue + + for nav in navigation: + if header_name in nav: + if filename == "getting-started/installation.md": + child_dir = "getting-started/install/" + children = os.listdir(docs_dir + "/" + child_dir) + children = [child_dir + c for c in children] + children.sort() + children.insert(0, "getting-started/advanced-installation.md") + if os.path.exists(docs_dir + "/" + filename): + children.insert(0, filename) + else: + children.insert(0, "getting-started/installation/index.md") + nav[header_name].append( + { + "Getting Started with Dokku": children, + } + ) + continue + + if filename == "getting-started/upgrading.md": + child_dir = "appendices/" + children = os.listdir(docs_dir + "/" + child_dir) + + children.sort( + key=lambda x: list(map(int, x.split("-")[0].split("."))) if "." in x else [-1, -1, -1], + reverse=True, + ) + children = [child_dir + c for c in children] + if os.path.exists(docs_dir + "/" + filename): + children.insert(0, filename) + else: + children.insert(0, "getting-started/upgrading/index.md") + nav[header_name].append( + { + "Upgrading": children, + } + ) + continue + + nav[header_name].append(filename) + return navigation + + +def generate_nav(src: str, dest: str) -> None: + """ + Writes out navigation information to the mkdocs yaml file + """ + navigation = [] + repo_dir = pathlib.Path(__file__).parent.parent.resolve() + docs_dir = str(repo_dir) + "/docs" + with open(docs_dir + "/template.html", encoding="utf-8") as response: + soup = BeautifulSoup(response, "html.parser") + selectors = [ + ".container .row .list-group a", + ".container-fluid .row .list-group a", + ] + for selector in selectors: + navigation = get_nav_from_selector(soup, selector, docs_dir) + if len(navigation) > 0: + break + + if len(navigation) == 0: + print("No navigation found") + sys.exit(1) + + with open(src, encoding="utf-8") as handler: + data = yaml.unsafe_load(handler) + data["nav"] = [ + {"Docs": navigation}, + {"Pro": "https://pro.dokku.com/docs/getting-started/"}, + {"Blog": "https://dokku.com/blog/"}, + {"Tutorials": "https://dokku.com/tutorials/"}, + { + "Purchase Dokku Pro": "https://dokku.dpdcart.com/cart/add?product_id=217344&method_id=236878" + }, + ] + + with open(dest, mode="wt", encoding="utf-8") as handler: + yaml.dump(data, handler) + + +def main(): + """ + Main command that performs doc manipulation + """ + print("----> Copying mkdocs.yml") + print(" Generating navigation") + generate_nav("/usr/src/source/mkdocs.yml", "/usr/src/app/mkdocs.yml") + + if os.path.exists("/usr/src/app/docs"): + print(" Removing old docs folder") + shutil.rmtree("/usr/src/app/docs") + + print(" Performing copy") + shutil.copytree("/usr/src/source/docs", "/usr/src/app/docs") + + +if __name__ == "__main__": + main() diff --git a/deb.mk b/deb.mk index 049df7f7332..528c0177e39 100644 --- a/deb.mk +++ b/deb.mk @@ -11,16 +11,12 @@ endif .PHONY: install-from-deb deb-all deb-dokku deb-setup install-from-deb: - @echo "--> Initial apt-get update" - sudo apt-get update -qq >/dev/null - sudo apt-get -qq -y --no-install-recommends install apt-transport-https - @echo "--> Installing docker" wget -nv -O - https://get.docker.com/ | sh @echo "--> Installing dokku" - wget -nv -O - https://packagecloud.io/dokku/dokku/gpgkey | apt-key add - - @echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ $(shell lsb_release -cs 2>/dev/null || echo "bionic") main" | sudo tee /etc/apt/sources.list.d/dokku.list + wget -qO- https://packagecloud.io/dokku/dokku/gpgkey | sudo tee /etc/apt/trusted.gpg.d/dokku.asc + @echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ $(shell lsb_release -cs 2>/dev/null || echo "noble") main" | sudo tee /etc/apt/sources.list.d/dokku.list sudo apt-get update -qq >/dev/null sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get -qq -y --no-install-recommends install dokku @@ -31,7 +27,7 @@ deb-all: deb-setup deb-dokku deb-setup: @echo "-> Updating deb repository and installing build requirements" @sudo apt-get update -qq >/dev/null - @sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get -qq -y --no-install-recommends install gcc git build-essential wget ruby-dev ruby1.9.1 lintian >/dev/null 2>&1 + @sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get -qq -y --no-install-recommends install gcc git build-essential wget ruby-dev lintian >/dev/null 2>&1 @command -v fpm >/dev/null || sudo gem install fpm --no-ri --no-rdoc @ssh -o StrictHostKeyChecking=no git@github.com || true @@ -43,10 +39,11 @@ ifneq (,$(findstring false,$(IS_RELEASE))) sed -i.bak -e "s/^/`date +%s`:/" /tmp/build-dokku/var/lib/dokku/STABLE_VERSION && rm /tmp/build-dokku/var/lib/dokku/STABLE_VERSION.bak endif + contrib/update-deb-dependencies cp -r debian /tmp/build-dokku/DEBIAN sed -i.bak "s/^Architecture: .*/Architecture: $(DOKKU_ARCHITECTURE)/g" /tmp/build-dokku/DEBIAN/control && rm /tmp/build-dokku/DEBIAN/control.bak rm -f /tmp/build-dokku/DEBIAN/lintian-overrides cp debian/lintian-overrides /tmp/build-dokku/usr/share/lintian/overrides/dokku sed -i.bak "s/^Version: .*/Version: `cat /tmp/build-dokku/var/lib/dokku/STABLE_VERSION`/g" /tmp/build-dokku/DEBIAN/control && rm /tmp/build-dokku/DEBIAN/control.bak dpkg-deb --build /tmp/build-dokku "$(BUILD_DIRECTORY)/dokku_`cat /tmp/build-dokku/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" - lintian "$(BUILD_DIRECTORY)/dokku_`cat /tmp/build-dokku/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" + lintian "$(BUILD_DIRECTORY)/dokku_`cat /tmp/build-dokku/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" || true diff --git a/debian/config b/debian/config index e4bcc25be12..2a7b4576a22 100755 --- a/debian/config +++ b/debian/config @@ -3,7 +3,6 @@ set -eo pipefail [[ $TRACE ]] && set -x if [[ -e /usr/share/debconf/confmodule ]]; then - # shellcheck disable=SC1091 . /usr/share/debconf/confmodule fi @@ -15,5 +14,6 @@ db_input "high" "dokku/hostname" || true db_input "high" "dokku/vhost_enable" || true if [ "$ACTION" != "reconfigure" ]; then db_input "high" "dokku/key_file" || true + db_input "high" "dokku/install_default_site" || true fi db_go || true diff --git a/debian/control b/debian/control index 1433fe1cf2e..bb2350c5a92 100644 --- a/debian/control +++ b/debian/control @@ -1,11 +1,11 @@ Package: dokku -Version: 0.26.8 +Version: 0.38.25 Section: web Priority: optional Architecture: amd64 -Depends: locales, git, cpio, curl, man-db, netcat, sshcommand (>= 0.12.0), docker-engine-cs (>= 17.05.0) | docker-engine (>= 17.05.0) | docker-io (>= 17.05.0) | docker.io (>= 17.05.0) | docker-ce (>= 17.05.0) | docker-ee (>= 17.05.0) | moby-engine, docker-image-labeler (>= 0.2.2), net-tools, netrc, software-properties-common, parallel, procfile-util (>= 0.11.0), python-software-properties | python3-software-properties, rsync, rsyslog, dos2unix, jq, unzip -Recommends: herokuish (>= 0.3.4), bash-completion, dokku-update, dokku-event-listener -Pre-Depends: gliderlabs-sigil, nginx (>= 1.8.0) | openresty, dnsutils, cgroupfs-mount | cgroup-lite, plugn (>= 0.3.0), sudo, python3, debconf +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 +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 Description: Docker-powered PaaS that helps build and manage the lifecycle of applications Dokku is an extensible, open source Platform as a Service diff --git a/debian/postinst b/debian/postinst index d7dab6306cd..6edbbed79ef 100755 --- a/debian/postinst +++ b/debian/postinst @@ -3,12 +3,10 @@ set -eo pipefail [[ $TRACE ]] && set -x if [[ -e /usr/share/debconf/confmodule ]]; then - # shellcheck disable=SC1091 . /usr/share/debconf/confmodule fi if [[ -r /etc/default/dokku ]]; then - # shellcheck disable=SC1091 source /etc/default/dokku fi @@ -26,6 +24,40 @@ call-sshcommand() { fi } +setup-docker-live-restore() { + # skip if running in docker-based installation + if [[ "$DOKKU_INIT_SYSTEM" == "sv" ]]; then + return + fi + + # allow disabling via /etc/default/dokku + if [[ "$DOKKU_LIVE_RESTORE" == "false" ]]; then + return + fi + + live_restore="$(docker info --format '{{ .LiveRestoreEnabled }}' || true)" + if [ "$live_restore" = "true" ]; then + echo "Docker live-restore is already enabled" + return + fi + + echo "Enabling docker live-restore" + if [[ ! -f /etc/docker/daemon.json ]]; then + mkdir -p /etc/docker + echo "{}" >/etc/docker/daemon.json + fi + + config="$(jq '. + {"live-restore": true}' /etc/docker/daemon.json)" + echo "$config" >/etc/docker/daemon.json + if command -v systemctl &>/dev/null; then + if ! systemctl reload docker; then + echo "Unable to reload docker, please reload manually" 1>&2 + fi + else + echo "Unable to reload docker, please reload manually" 1>&2 + fi +} + setup-user() { echo "Setting up dokku user" call-sshcommand create dokku /usr/bin/dokku @@ -86,7 +118,8 @@ setup-plugins() { PLUGIN_PATH="${DOKKU_LIB_ROOT}/plugins" plugn enable "$plugin" fi done - find -L "${DOKKU_LIB_ROOT}" -type l -delete + find -L "${DOKKU_LIB_ROOT}/core-plugins" -type l -delete + find -L "${DOKKU_LIB_ROOT}/plugins" -type l -delete chown dokku:dokku -R "${DOKKU_LIB_ROOT}/plugins" "${DOKKU_LIB_ROOT}/core-plugins" echo "Install all core plugins" @@ -102,6 +135,85 @@ setup-sshcommand() { fi } +disable-default-vhost-path() { + local path="$1" + local backup_path="${path}.dokku-disabled" + + if [ ! -e "$path" ] && [ ! -L "$path" ]; then + return + fi + + if [ -e "$backup_path" ] || [ -L "$backup_path" ]; then + return + fi + + case "$path" in + /etc/nginx/sites-enabled/*) + rm -f "$path" + echo "Removed conflicting nginx default at $path" 1>&2 + return + ;; + esac + + if [ -L "$path" ]; then + rm -f "$path" + echo "Removed conflicting nginx default symlink at $path" 1>&2 + return + fi + + mv -n "$path" "$backup_path" + echo "Disabled $path (renamed to $backup_path)" 1>&2 +} + +setup-default-site() { + db_get "dokku/install_default_site" + if [ "$RET" != "true" ]; then + return + fi + + local nginx_bin nginx_version major minor patch + nginx_bin="$(command -v nginx || true)" + if [ -z "$nginx_bin" ]; then + return + fi + + nginx_version="$("$nginx_bin" -v 2>&1 | cut -d'/' -f 2 | awk '{print $1}')" + major="$(echo "$nginx_version" | awk -F. '{print $1}')" + minor="$(echo "$nginx_version" | awk -F. '{print $2}')" + patch="$(echo "$nginx_version" | awk -F. '{print $3}')" + + # ssl_reject_handshake requires nginx >= 1.19.4; older nginx gets the + # HTTP-only catch-all so the SSL listen lines do not require a cert. + local default_vhost_basename="default-site.conf" + if [ "${major:-0}" -lt 2 ]; then + if [ "${major:-0}" -lt 1 ] || [ "${minor:-0}" -lt 19 ] || { [ "${minor:-0}" -eq 19 ] && [ "${patch:-0}" -lt 4 ]; }; then + default_vhost_basename="default-site-legacy.conf" + fi + fi + + local default_vhost_target="/etc/nginx/conf.d/00-default-vhost.conf" + local default_vhost_source="${DOKKU_LIB_ROOT}/core-plugins/available/nginx-vhosts/templates/${default_vhost_basename}" + + if [ -e "$default_vhost_target" ]; then + return + fi + + if [ ! -f "$default_vhost_source" ]; then + return + fi + + for path in /etc/nginx/sites-enabled/default /etc/nginx/sites-available/default /etc/nginx/conf.d/default.conf; do + disable-default-vhost-path "$path" + done + + echo "Installing nginx default catch-all site at $default_vhost_target" + install -m 0644 -o root -g root "$default_vhost_source" "$default_vhost_target" + + if command -v dokku &>/dev/null; then + dokku nginx:reload || true + fi +} + dpkg-handling() { if [ -f "${DOKKU_ROOT}/VHOST" ]; then echo "VHOST file detected, skipping modification" @@ -111,6 +223,7 @@ dpkg-handling() { db_get "dokku/hostname" echo "Setting VHOST contents to $RET" echo "$RET" >"${DOKKU_ROOT}/VHOST" + chown dokku:dokku "${DOKKU_ROOT}/VHOST" fi fi @@ -125,13 +238,9 @@ dpkg-handling() { case "$1" in abort-upgrade | abort-remove | abort-deconfigure) ;; - \ - configure) + configure) mandb [ ! -x /usr/bin/docker.io ] || ln -sf /usr/bin/docker.io /usr/local/bin/docker - if [[ -f /sbin/modprobe ]]; then - modprobe aufs || echo "WARNING: Restart server to finish installing dokku!" - fi setup-user setup-storage @@ -139,6 +248,10 @@ case "$1" in dpkg-handling setup-plugins setup-sshcommand + setup-docker-live-restore + if [ -z "$2" ]; then + setup-default-site + fi ;; *) diff --git a/debian/postrm b/debian/postrm index 6c15a4d0940..78f3b83de4a 100755 --- a/debian/postrm +++ b/debian/postrm @@ -3,7 +3,6 @@ set -eo pipefail [[ $TRACE ]] && set -x if [[ -e /usr/share/debconf/confmodule ]]; then - # shellcheck disable=SC1091 . /usr/share/debconf/confmodule fi diff --git a/debian/preinst b/debian/preinst index 7493eca0543..fb7104341dd 100755 --- a/debian/preinst +++ b/debian/preinst @@ -3,7 +3,6 @@ set -eo pipefail [[ $TRACE ]] && set -x if [[ -e /usr/share/debconf/confmodule ]]; then - # shellcheck disable=SC1091 . /usr/share/debconf/confmodule fi @@ -22,11 +21,9 @@ case "$1" in upgrade) ;; - \ - abort-upgrade) ;; + abort-upgrade) ;; - \ - *) + *) echo "preinst called with unknown argument \`$1'" >&2 exit 1 ;; diff --git a/debian/prerm b/debian/prerm index 33ec447df77..751e338a516 100755 --- a/debian/prerm +++ b/debian/prerm @@ -11,39 +11,39 @@ ps_backtrace() { exit 1 fi - declare -i pid=$1 + declare -i pid="$1" ppid=0 header_modifier="" while :; do - if [ $ppid -ne 0 ]; then + if [[ "$ppid" -ne 0 ]]; then header_modifier=h fi - ppid=$(ps -o ppid= $pid) - ps uww $header_modifier -p $pid - if [ $pid -eq 1 ]; then + ppid="$(ps -o ppid= "$pid")" + ps uww $header_modifier -p "$pid" + if [[ "$pid" -eq 1 ]] || [[ "$pid" -eq 0 ]]; then break fi - pid=$ppid + pid="$ppid" done } main() { # HACK: Ensure that we only delete data when purging dokku from the system - if ps_backtrace $BASHPID 2>/dev/null | grep dpkg | grep -- "--purge" >/dev/null; then + if ps_backtrace "$BASHPID" 2>/dev/null | grep dpkg | grep -- "--purge" >/dev/null; then echo "Processing purge" echo "Destroying deployed applications" for app in $(DOKKU_QUIET_OUTPUT=1 dokku apps:list); do - dokku --force apps:destroy $app + dokku --force apps:destroy "$app" done # HACK: Only disable core plugins, as we don't know what data users store in non-core plugin directories echo "Disabling 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 - rm ${DOKKU_LIB_ROOT}/plugins/available/$plugin - PLUGIN_PATH=${DOKKU_LIB_ROOT}/core-plugins plugn disable $plugin - PLUGIN_PATH=${DOKKU_LIB_ROOT}/plugins plugn disable $plugin + 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 + rm "${DOKKU_LIB_ROOT}/plugins/available/$plugin" + PLUGIN_PATH="${DOKKU_LIB_ROOT}/core-plugins" plugn disable "$plugin" + PLUGIN_PATH="${DOKKU_LIB_ROOT}/plugins" plugn disable "$plugin" fi done else diff --git a/debian/templates b/debian/templates index 93e7a9768d4..51403a24eec 100644 --- a/debian/templates +++ b/debian/templates @@ -22,3 +22,8 @@ Template: dokku/nginx_enable Description: Enable nginx-vhosts plugin? Type: boolean Default: true + +Template: dokku/install_default_site +Description: Install a catch-all nginx default site on fresh install? +Type: boolean +Default: true diff --git a/docker/etc/cron.d/docker-builder-prune b/docker/etc/cron.d/docker-builder-prune new file mode 100644 index 00000000000..975ce4f53e1 --- /dev/null +++ b/docker/etc/cron.d/docker-builder-prune @@ -0,0 +1,4 @@ +PATH=/usr/local/bin:/usr/bin:/bin +SHELL=/bin/bash + +5 4 * * * root /usr/local/bin/docker builder prune -f >>/var/log/dokku/builder-prune.log 2>&1 diff --git a/docker/etc/my_init.d/10_dokku_init b/docker/etc/my_init.d/10_dokku_init index f495142d4d7..c740a3a093b 100755 --- a/docker/etc/my_init.d/10_dokku_init +++ b/docker/etc/my_init.d/10_dokku_init @@ -3,19 +3,28 @@ set -eo pipefail log-info() { declare desc="Log info formatter" - echo " $*" 1>&2 + echo " $*" 1>&2 } log-fail() { declare desc="Log fail formatter" - echo "! $*" 1>&2 + echo " ! $*" 1>&2 exit 1 } +log-warn() { + declare desc="Log fail formatter" + echo " ! $*" 1>&2 +} + support-userns() { chown 0:0 /usr/bin/sudo chmod 4755 /usr/bin/sudo - chown 0:0 /usr/lib/sudo/sudoers.so + if [[ -f /usr/lib/sudo/sudoers.so ]]; then + chown 0:0 /usr/lib/sudo/sudoers.so + elif [[ -f /usr/libexec/sudo/sudoers.so ]]; then + chown 0:0 /usr/libexec/sudo/sudoers.so + fi chown -R 0:0 /etc/sudoers chown -R 0:0 /etc/sudoers.d chown -R 0:0 /run/sshd @@ -26,47 +35,70 @@ main() { support-userns + rm -f /var/run/dokku/ready + mkdir -p /var/run/dokku + if [[ ! -d /mnt/dokku ]]; then log-info "Creating missing /mnt/dokku" mkdir -p /mnt/dokku fi - directories=("/etc/ssh" "/home/dokku" "/var/lib/dokku/config" "/var/lib/dokku/data") + directories=("/etc/ssh" "/home/dokku" "/var/lib/dokku/config" "/var/lib/dokku/data" "/var/lib/dokku/services") for dir in "${directories[@]}"; do if [[ ! -e "$dir" ]]; then log-info "Copying ${dir} skel into place" mkdir -p "/mnt/dokku${dir}" - rsync -a "/skel${dir}/" "${dir}/" || log-fail "Unable to copy ${dir} skel into place" + if [[ -d "/skel${dir}" ]]; then + rsync -a "/skel${dir}/" "${dir}/" || log-fail "Unable to copy ${dir} skel into place" + fi fi # chown mount directories to "dokku:dokku" - if [[ "$dir" != "/etc/ssh" ]]; then + if [[ "$dir" == "/var/lib/dokku/services" ]]; then + chown dokku:dokku /var/lib/dokku/services + elif [[ "$dir" != "/etc/ssh" ]]; then find "$dir" -print0 | xargs -0 chown -h dokku:dokku fi done + touch /etc/default/dokku + echo "export DOKKU_INIT_SYSTEM=sv" >>/etc/default/dokku + if [[ -x /usr/local/bin/docker ]]; then + echo "export DOCKER_BIN=/usr/local/bin/docker" >>/etc/default/dokku + fi + + if [[ -n "$DOKKU_HOST_ROOT" ]]; then + echo "export DOKKU_HOST_ROOT=$DOKKU_HOST_ROOT" >>/etc/default/dokku + chown dokku:dokku /etc/default/dokku + fi + + if [[ -n "$DOKKU_LIB_HOST_ROOT" ]]; then + echo "export DOKKU_LIB_HOST_ROOT=$DOKKU_LIB_HOST_ROOT" >>/etc/default/dokku + chown dokku:dokku /etc/default/dokku + fi + if [[ -f /mnt/dokku/plugin-list ]]; then while read line; do - dokku plugin:install "$(echo "$line" | awk '{print $2}')" "$(echo "$line" | cut -d':' -f1)" + local plugin_name="$(echo "$line" | awk -F: '{print $1}')" + if dokku plugin:installed "$plugin_name"; then + log-warn "Skipping already installed plugin: $plugin_name" + else + dokku plugin:install "$(echo "$line" | awk -F': ' '{print $2}')" --name "$plugin_name" + fi done "/home/dokku/.dokkurc/DOCKER_BIN" - fi - - if [[ -n "$DOKKU_HOST_ROOT" ]]; then - touch /etc/default/dokku - echo "export DOKKU_HOST_ROOT=$DOKKU_HOST_ROOT" >>/etc/default/dokku - chown dokku:dokku /etc/default/dokku - fi + echo "dokku dokku/skip_key_file boolean true" | debconf-set-selections + echo "dokku dokku/key_file string /root/.ssh/id_rsa.pub" | debconf-set-selections NGINX_ROOT="/etc/nginx" if [[ -x /usr/bin/openresty ]]; then diff --git a/docker/etc/nginx/conf.d/zz-dokku-health.conf b/docker/etc/nginx/conf.d/zz-dokku-health.conf new file mode 100644 index 00000000000..92c1475e387 --- /dev/null +++ b/docker/etc/nginx/conf.d/zz-dokku-health.conf @@ -0,0 +1,16 @@ +# Internal health endpoint for the official dokku/dokku Docker image. +# Bound to loopback so it is reachable only from inside the container +# and never conflicts with user app vhosts on 80/443. +server { + listen 127.0.0.1:18080; + server_name _; + access_log off; + default_type text/plain; + + location = /_dokku/health { + if (-f /var/run/dokku/ready) { + return 200 "ok\n"; + } + return 503 "not ready\n"; + } +} diff --git a/docker/etc/runit/runsvdir/default/dokku-restore/finish b/docker/etc/runit/runsvdir/default/dokku-restore/finish index ea80c20c0f5..39a01df0bfe 100755 --- a/docker/etc/runit/runsvdir/default/dokku-restore/finish +++ b/docker/etc/runit/runsvdir/default/dokku-restore/finish @@ -10,6 +10,10 @@ if [[ "$1" -ne 0 ]]; then fi echo "Done restoring dokku apps." -exec sleep infinity -exit 0 +while ! (echo >/dev/tcp/127.0.0.1/22) >/dev/null 2>&1; do sleep 1; done +while ! (echo >/dev/tcp/127.0.0.1/80) >/dev/null 2>&1; do sleep 1; done +touch /var/run/dokku/ready +echo "Dokku is ready." + +exec sleep infinity diff --git a/docker/etc/ssh/sshd_config b/docker/etc/ssh/sshd_config new file mode 100644 index 00000000000..634ce8b2770 --- /dev/null +++ b/docker/etc/ssh/sshd_config @@ -0,0 +1,133 @@ +# $OpenBSD: sshd_config,v 1.80 2008/07/02 02:24:18 djm Exp $ + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options change a +# default value. + +UseDNS no + +Port 22 +Port 22333 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +# Disable legacy (protocol version 1) support in the server for new +# installations. In future the default will change to require explicit +# activation of protocol 1 +Protocol 2 + +# HostKey for protocol version 1 +#HostKey /etc/ssh_host_key +# HostKeys for protocol version 2 +#HostKey /etc/ssh_host_rsa_key +#HostKey /etc/ssh_host_dsa_key + +# Lifetime and size of ephemeral version 1 server key +#KeyRegenerationInterval 1h +#ServerKeyBits 1024 + +# Logging +# obsoletes QuietMode and FascistLogging +SyslogFacility AUTHPRIV +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +#PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#RSAAuthentication yes +#PubkeyAuthentication yes +#AuthorizedKeysFile .ssh/authorized_keys + +# For this to work you will also need host keys in /etc/ssh_known_hosts +#RhostsRSAAuthentication no +# similar for protocol version 2 +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# RhostsRSAAuthentication and HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! Also, +# remember to set the UsePAM setting to 'no'. +PasswordAuthentication no +#PermitEmptyPasswords no + +# SACL options +# The default for the SACLSupport option is now "no", as this option has been +# depreciated in favor of SACL enforcement in the PAM configuration (/etc/pam.d/sshd). +#SACLSupport no + +# Change to no to disable s/key passwords +# Disabled for passenger-docker. We only allow key authentication. +ChallengeResponseAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes +#GSSAPIStrictAcceptorCheck yes +#GSSAPIKeyExchange no + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via ChallengeResponseAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and ChallengeResponseAuthentication to 'no'. +# Also, PAM will deny null passwords by default. If you need to allow +# null passwords, add the " nullok" option to the end of the +# securityserver.so line in /etc/pam.d/sshd. +UsePAM yes + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +X11Forwarding yes +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#UseLogin no +#UsePrivilegeSeparation yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS yes +#PidFile /var/run/sshd.pid +#MaxStartups 10 +#PermitTunnel no +#ChrootDirectory none + +# no default banner path +#Banner none + +# override default of no subsystems +Subsystem sftp /usr/lib/openssh/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# ForceCommand cvs server \ No newline at end of file diff --git a/docker/etc/sudoers.d/dokku-docker b/docker/etc/sudoers.d/dokku-docker index ae9a60f3f71..f3dc76faa3c 100644 --- a/docker/etc/sudoers.d/dokku-docker +++ b/docker/etc/sudoers.d/dokku-docker @@ -1 +1 @@ -dokku ALL=NOPASSWD:SETENV:/usr/bin/docker,/usr/bin/docker-image-labeler,/usr/bin/pack +dokku ALL=NOPASSWD:SETENV:/usr/bin/docker,/usr/bin/docker-container-healthchecker,/usr/bin/docker-image-labeler,/usr/bin/lambda-builder,/usr/bin/nixpacks,/usr/bin/railpack,/usr/bin/pack diff --git a/docker/usr/local/bin/lambda-builder b/docker/usr/local/bin/lambda-builder new file mode 100644 index 00000000000..cc1b50e9c4d --- /dev/null +++ b/docker/usr/local/bin/lambda-builder @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x + +main() { + declare desc="re-runs lambda-builder commands as sudo" + local LAMBDA_BUILDER_BIN="" + if [[ -x "/usr/bin/lambda-builder" ]]; then + LAMBDA_BUILDER_BIN="/usr/bin/lambda-builder" + fi + + if [[ -z "$LAMBDA_BUILDER_BIN" ]]; then + echo "! No lambda-builder binary found" 1>&2 + exit 1 + fi + + sudo -E "$LAMBDA_BUILDER_BIN" "$@" +} + +main "$@" diff --git a/docker/usr/local/bin/nixpacks b/docker/usr/local/bin/nixpacks new file mode 100644 index 00000000000..e690254b9cd --- /dev/null +++ b/docker/usr/local/bin/nixpacks @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x + +main() { + declare desc="re-runs nixpacks commands as sudo" + local NIXPACKS_BIN="" + if [[ -x "/usr/bin/nixpacks" ]]; then + NIXPACKS_BIN="/usr/bin/nixpacks" + fi + + if [[ -z "$NIXPACKS_BIN" ]]; then + echo "! No nixpacks binary found" 1>&2 + exit 1 + fi + + sudo -E "$NIXPACKS_BIN" "$@" +} + +main "$@" diff --git a/docker/usr/local/bin/railpack b/docker/usr/local/bin/railpack new file mode 100644 index 00000000000..35bee71d2fe --- /dev/null +++ b/docker/usr/local/bin/railpack @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x + +main() { + declare desc="re-runs railpack commands as sudo" + local RAILPACK_BIN="" + if [[ -x "/usr/bin/railpack" ]]; then + RAILPACK_BIN="/usr/bin/railpack" + fi + + if [[ -z "$RAILPACK_BIN" ]]; then + echo "! No railpack binary found" 1>&2 + exit 1 + fi + + sudo -E "$RAILPACK_BIN" "$@" +} + +main "$@" diff --git a/docs.mk b/docs.mk new file mode 100644 index 00000000000..f9c1f4717b0 --- /dev/null +++ b/docs.mk @@ -0,0 +1,19 @@ +.PHONY: docs-build-image +docs-build-image: + docker build -f docs/_build/Dockerfile --progress plain -t app/mkdocs . + +.PHONY: docs-build +docs-build: + @docker run --rm -p 3487:3487 -v "$(PWD):/usr/src/source" --name docs-serve app/mkdocs build + +.PHONY: docs-serve +docs-serve: + docker run --rm -p 3487:3487 -v "$(PWD):/usr/src/source" --name docs-serve app/mkdocs serve + +.PHONY: docs-deps +docs-deps: + @docker run --rm -p 3487:3487 -v "$(PWD):/usr/src/app" app/mkdocs deps + +.PHONY: docs-enter +docs-enter: + docker exec -it docs-serve bash diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..00e8acd4abc --- /dev/null +++ b/docs/README.md @@ -0,0 +1,161 @@ +# Dokku Documentation + +This documentation covers the installation, configuration, and usage of Dokku - a Docker-powered PaaS that provides a Heroku-like experience. + +## Getting Started + +- [Installation](getting-started/installation/index.md) - Install Dokku on your server + - [Debian](getting-started/install/debian.md) + - [Docker](getting-started/install/docker.md) + - [DigitalOcean](getting-started/install/digitalocean.md) + - [Azure](getting-started/install/azure.md) + - [DreamHost](getting-started/install/dreamhost.md) + - [Vagrant](getting-started/install/vagrant.md) +- [Advanced Installation](getting-started/advanced-installation.md) - Custom installation options +- [Upgrading](getting-started/upgrading/index.md) - Upgrade to newer Dokku versions +- [Uninstalling](getting-started/uninstalling.md) - Remove Dokku from your server +- [Troubleshooting](getting-started/troubleshooting.md) - Common issues and solutions +- [Where to Get Help](getting-started/where-to-get-help.md) - Support resources + +## Deployment + +- [Application Deployment](deployment/application-deployment.md) - Deploy your first app +- [Application Management](deployment/application-management.md) - Manage deployed applications +- [Logs](deployment/logs.md) - View application logs +- [Remote Commands](deployment/remote-commands.md) - Run commands remotely +- [User Management](deployment/user-management.md) - Manage SSH access +- [Zero Downtime Deploys](deployment/zero-downtime-deploys.md) - Deploy without interruption + +### Deployment Methods + +- [Git](deployment/methods/git.md) - Deploy via git push +- [Archive](deployment/methods/archive.md) - Deploy from tar/zip archives +- [Image](deployment/methods/image.md) - Deploy from Docker images + +### Builders + +- [Builder Management](deployment/builders/builder-management.md) - Configure build systems +- [Buildpack Management](deployment/builders/buildpack-management.md) - Manage buildpack settings +- [Herokuish Buildpacks](deployment/builders/herokuish-buildpacks.md) - Heroku-compatible buildpacks +- [Cloud Native Buildpacks](deployment/builders/cloud-native-buildpacks.md) - CNB support +- [Dockerfiles](deployment/builders/dockerfiles.md) - Build from Dockerfile +- [Nixpacks](deployment/builders/nixpacks.md) - Nixpacks builder +- [Railpack](deployment/builders/railpack.md) - Railpack builder +- [Lambda](deployment/builders/lambda.md) - Lambda-style functions +- [Null Builder](deployment/builders/null.md) - Skip the build phase + +### Schedulers + +- [Scheduler Management](deployment/schedulers/scheduler-management.md) - Configure schedulers +- [Docker Local](deployment/schedulers/docker-local.md) - Default Docker scheduler +- [K3s](deployment/schedulers/k3s.md) - Lightweight Kubernetes +- [Kubernetes](deployment/schedulers/kubernetes.md) - External Kubernetes clusters +- [Nomad](deployment/schedulers/nomad.md) - HashiCorp Nomad +- [Null Scheduler](deployment/schedulers/null.md) - Skip scheduling + +### Continuous Integration + +- [Generic CI](deployment/continuous-integration/generic.md) - General CI/CD setup +- [GitHub Actions](deployment/continuous-integration/github-actions.md) +- [GitLab CI](deployment/continuous-integration/gitlab-ci.md) +- [Woodpecker CI](deployment/continuous-integration/woodpecker-ci.md) + +## Configuration + +- [Environment Variables](configuration/environment-variables.md) - Configure app environment +- [Domains](configuration/domains.md) - Custom domain management +- [SSL/TLS](configuration/ssl.md) - HTTPS and certificates + +## Networking + +- [Proxy Management](networking/proxy-management.md) - Reverse proxy configuration +- [Port Management](networking/port-management.md) - Port mapping and exposure +- [DNS](networking/dns.md) - DNS configuration +- [Network](networking/network.md) - Docker network management + +### Proxies + +- [Nginx](networking/proxies/nginx.md) - Default proxy +- [Caddy](networking/proxies/caddy.md) +- [HAProxy](networking/proxies/haproxy.md) +- [Traefik](networking/proxies/traefik.md) +- [OpenResty](networking/proxies/openresty.md) + +## Processes + +- [Process Management](processes/process-management.md) - Scale and manage processes +- [Scheduled Cron Tasks](processes/scheduled-cron-tasks.md) - Scheduled jobs +- [One-off Tasks](processes/one-off-tasks.md) - Run one-time commands +- [Entering Containers](processes/entering-containers.md) - Access running containers + +## Advanced Usage + +- [Persistent Storage](advanced-usage/persistent-storage.md) - Mount volumes +- [Docker Options](advanced-usage/docker-options.md) - Custom Docker run arguments +- [Resource Management](advanced-usage/resource-management.md) - CPU and memory limits +- [Plugin Management](advanced-usage/plugin-management.md) - Install and manage plugins +- [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 +- [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 + +## Development + +- [Architecture](development/architecture.md) - Internal architecture overview for contributors +- [Plugin Creation](development/plugin-creation.md) - Build custom plugins +- [Plugin Triggers](development/plugin-triggers.md) - Available plugin hooks +- [Testing](development/testing.md) - Test Dokku and plugins +- [Release Process](development/release-process.md) - How Dokku releases work + +## Community + +- [Plugins](community/plugins.md) - Community-maintained plugins +- [Clients](community/clients.md) - API clients and tools + +## Enterprise + +- [Dokku Pro](enterprise/pro.md) - Commercial features and support + +## Appendices + +### Migration Guides + +- [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) +- [0.35.0](appendices/0.35.0-migration-guide.md) +- [0.34.0](appendices/0.34.0-migration-guide.md) +- [0.33.0](appendices/0.33.0-migration-guide.md) +- [0.32.0](appendices/0.32.0-migration-guide.md) +- [0.31.0](appendices/0.31.0-migration-guide.md) +- [0.30.0](appendices/0.30.0-migration-guide.md) +- [0.29.0](appendices/0.29.0-migration-guide.md) +- [0.28.0](appendices/0.28.0-migration-guide.md) +- [0.27.0](appendices/0.27.0-migration-guide.md) +- [0.26.0](appendices/0.26.0-migration-guide.md) +- [0.25.0](appendices/0.25.0-migration-guide.md) +- [0.24.0](appendices/0.24.0-migration-guide.md) +- [0.23.0](appendices/0.23.0-migration-guide.md) +- [0.22.0](appendices/0.22.0-migration-guide.md) +- [0.21.0](appendices/0.21.0-migration-guide.md) +- [0.20.0](appendices/0.20.0-migration-guide.md) +- [0.10.0](appendices/0.10.0-migration-guide.md) +- [0.9.0](appendices/0.9.0-migration-guide.md) +- [0.8.0](appendices/0.8.0-migration-guide.md) +- [0.7.0](appendices/0.7.0-migration-guide.md) +- [0.6.0](appendices/0.6.0-migration-guide.md) +- [0.5.0](appendices/0.5.0-migration-guide.md) + +### File Formats + +- [app.json](appendices/file-formats/app-json.md) - Application configuration +- [Procfile](appendices/file-formats/procfile.md) - Process definitions +- [Dockerfile](appendices/file-formats/dockerfile.md) - Docker build instructions +- [.buildpacks](appendices/file-formats/buildpacks-file.md) - Buildpack configuration +- [project.toml](appendices/file-formats/project-toml.md) - CNB configuration +- [nixpacks.toml](appendices/file-formats/nixpacks-toml.md) - Nixpacks configuration +- [railpack.json](appendices/file-formats/railpack-json.md) - Railpack configuration +- [lambda.yml](appendices/file-formats/lambda-yml.md) - Lambda configuration +- [nginx.conf.sigil](appendices/file-formats/nginx-conf-sigil.md) - Nginx template customization diff --git a/docs/_build/Dockerfile b/docs/_build/Dockerfile new file mode 100644 index 00000000000..b56a07f2e44 --- /dev/null +++ b/docs/_build/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.15.0b4-alpine + +WORKDIR /usr/src/app + +COPY docs/_build/requirements.txt . + +RUN apk add --no-cache bash build-base yaml && \ + pip install --no-cache-dir -r requirements.txt + +COPY docs/_build/entrypoint /bin/entrypoint + +ENTRYPOINT [ "/bin/entrypoint" ] diff --git a/docs/_build/app-nginx.conf.sigil b/docs/_build/app-nginx.conf.sigil new file mode 100644 index 00000000000..fff079520d4 --- /dev/null +++ b/docs/_build/app-nginx.conf.sigil @@ -0,0 +1,26 @@ +worker_processes 1; +error_log stderr; +pid nginx.pid; +daemon off; + +events { + worker_connections 768; +} + +http { + types_hash_max_size 2048; + include mime.types; + charset UTF-8; + server { + listen {{ $.PORT }}; + server_name _; + root /app/www; + index index.html; + port_in_redirect off; + + include /app/www/nginx.conf.d/*.conf; + location / { + try_files $uri $uri/ =404; + } + } +} diff --git a/docs/_build/entrypoint b/docs/_build/entrypoint new file mode 100755 index 00000000000..9d01485286a --- /dev/null +++ b/docs/_build/entrypoint @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -eo pipefail + +write-mkdocs() { + if [[ -f /usr/src/source/contrib/write-mkdocs ]]; then + if ! python3 /usr/src/source/contrib/write-mkdocs; then + exit 0 + fi + fi +} + +main() { + declare CMD="$1" ARGS=("${@:2}") + + if [[ "$CMD" == "build" ]]; then + write-mkdocs + echo "----> Executing mkdocs build" + mkdocs build + site_dir="$(grep site_dir mkdocs.yml | awk '{print $2}')" + cp -r "/usr/src/app/${site_dir}" "/usr/src/source/${site_dir}" + + return $? + fi + + if [[ "$CMD" == "deps" ]]; then + exec pip freeze + fi + + if [[ "$CMD" == "serve" ]]; then + write-mkdocs + exec mkdocs serve -a 0.0.0.0:3487 + fi + + exec "$CMD" "${ARGS[@]}" +} + +main "$@" diff --git a/docs/_build/hooks.py b/docs/_build/hooks.py new file mode 100644 index 00000000000..fe3ac182305 --- /dev/null +++ b/docs/_build/hooks.py @@ -0,0 +1,407 @@ +#!/usr/bin/env python + +import posixpath +import re + +from collections.abc import Callable +from re import Match +from mkdocs.config.defaults import MkDocsConfig +from mkdocs.structure.files import File, Files +from mkdocs.structure.pages import Page + + +def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files) -> str | None: + resp = re_on_page_markdown(markdown, page=page, config=config, files=files) + if not resp: + return resp + + + modifiers: list[Callable[[list[str], str], tuple[list[str], bool]]] = [ + modify_content_noop, + modify_content_links, + modify_content_stripspace, + modify_content_admonition, + modify_content_terminal_example, + ] + + is_modified = False + lines = resp.split("\n") + for modifier in modifiers: + lines, modified = modifier(lines, page.file.src_path) + if modified: + is_modified = True + + if is_modified: + resp = "\n".join(lines) + + return resp + + +def modify_content_noop(lines: list[str], _) -> tuple[list[str], bool]: + """ + Simply returns the lines as is + """ + modified = False + updated_lines = [] + for line in lines: + updated_lines.append(line) + return updated_lines, modified + + +def modify_content_links(lines: list[str], filename: str) -> tuple[list[str], bool]: + """ + Modifies links to be relative instead of absolute + """ + filename = filename.replace("/usr/src/source/docs/", "") + parts = filename.split("/") + parts.pop() + replacement = "](" + "/".join([".." for _ in parts]) + "/" + modified = False + updated_lines = [] + for line in lines: + if "](/docs/" in line: + line = line.replace("](/docs/", replacement) + modified = True + + updated_lines.append(line) + return updated_lines, modified + + +def modify_content_stripspace(lines: list[str], _) -> tuple[list[str], bool]: + """ + Removes trailing whitespace from each line + """ + modified = False + updated_lines = [] + for line in lines: + line = line.rstrip() + updated_lines.append(line) + return updated_lines, modified + + +def is_github_new(line: str, next_line: str | None) -> bool: + """ + Checks if a given line is a github "new as of" admonition + """ + if not next_line: + return False + + return line.startswith("> [!IMPORTANT]") and "new as of" in next_line.lower() + + +def is_github_note(line: str) -> bool: + """ + Checks if a given line is a github "note" admonition + """ + return line.startswith("> [!NOTE]") + + +def is_github_warning(line: str) -> bool: + """ + Checks if a given line is a github "warning" admonition + """ + return line.startswith("> [!WARNING]") + + +def is_info(line: str) -> bool: + """ + Checks if a given line is an "info" admonition + """ + return line.startswith("> ") + + +def is_new(line: str) -> bool: + """ + Checks if a given line is a "new as of" admonition + """ + return line.startswith("> ") and "new as of" in line.lower() + + +def is_note(line: str) -> bool: + """ + Checks if a given line is a "note" admonition + """ + return line.startswith("> Note:") + + +def is_warning(line: str) -> bool: + """ + Checks if a given line is a "warning" admonition + """ + return line.startswith("> Warning:") + + +def modify_content_admonition(lines: list[str], filename: str) -> tuple[list[str], bool]: + """ + Applies adminition info to each line in the output + """ + modified = False + updated_lines = [] + admonition_lines = [] + is_admonition = False + replace_new_in_next_line = False + for index, line in enumerate(lines): + next_line: str | None = None + if index + 1 < len(lines): + next_line = lines[index + 1] + + if replace_new_in_next_line: + line = line.replace("New as of", "Introduced in") + line = line.replace("new as of", "introduced in") + replace_new_in_next_line = False + + if is_github_new(line, next_line): + line = line.replace("> [!IMPORTANT]", '!!! tip "New"') + is_admonition = True + admonition_lines.append(line) + admonition_lines.append("") + replace_new_in_next_line = True + elif is_github_note(line): + line = line.replace("> [!NOTE]", '!!! note "Note"') + is_admonition = True + admonition_lines.append(line) + admonition_lines.append("") + elif is_github_warning(line): + line = line.replace("> [!WARNING]", '!!! warning "Warning"') + is_admonition = True + admonition_lines.append(line) + admonition_lines.append("") + elif is_new(line): + print("is_new") + line = line.replace("> ", '!!! tip "New"\n\n ') + line = line.replace("New as of", "Introduced in") + line = line.replace("new as of", "introduced in") + is_admonition = True + admonition_lines.append(line) + elif is_note(line): + print("is_note") + line = line.replace("> Note: ", '!!! note "Note"\n\n ') + is_admonition = True + admonition_lines.append(line) + elif is_warning(line): + print("is_warning") + line = line.replace("> Warning: ", '!!! warning "Warning"\n\n ') + is_admonition = True + admonition_lines.append(line) + elif is_info(line): + if not is_admonition: + line = line.replace("> ", '!!! info "Info"\n\n ') + elif line in [">", "> "]: + line = "" + else: + line = " " + line.removeprefix("> ") + is_admonition = True + admonition_lines.append(line) + elif is_admonition and line in [">", "> "]: + line = "" + admonition_lines.append(line) + elif is_admonition and line.startswith("> "): + line = " " + line.removeprefix("> ") + admonition_lines.append(line) + elif line == "": + is_admonition = False + if len(admonition_lines) > 0: + modified = True + updated_lines.extend(admonition_lines) + admonition_lines = [] + updated_lines.append("") + else: + updated_lines.append(line) + return updated_lines, modified + + +def is_shell_codeblock_start(line: str) -> bool: + """ + Checks to see if a line starts a codeblock + """ + return line == "```shell" + + +def modify_content_terminal_example(lines: list[str], _) -> tuple[list[str], bool]: + """ + Modifies content so that terminal output is shown appropriately + """ + modified = False + updated_lines = [] + command_block = [] + example_block = [] + in_command_block = False + in_example_block = False + previous_block = "" + next_line_must_be = None + for line in lines: + if is_shell_codeblock_start(line): + command_block.append(line) + modified = True + in_command_block = True + continue + elif in_command_block: + command_block.append(line) + if line == "```": + in_command_block = False + previous_block = "command_block" + next_line_must_be = "" + continue + elif line == "```": + if previous_block == "": + updated_lines.append(line) + continue + if previous_block == "command_block": + example_block.append(line) + + if in_example_block: + previous_block = "" + in_example_block = False + updated_lines.append('=== "Shell"') + updated_lines.append("") + for command_line in command_block: + updated_lines.append(f" {command_line}") + command_block = [] + + updated_lines.append("") + updated_lines.append('=== "Output"') + updated_lines.append("") + for example_line in example_block: + updated_lines.append(f" {example_line}") + example_block = [] + else: + in_example_block = True + continue + elif previous_block == "command_block": + if next_line_must_be is None: + if in_example_block: + example_block.append(line) + else: + updated_lines.extend(command_block) + updated_lines.append("") + updated_lines.append(line) + command_block = [] + previous_block = "" + continue + if next_line_must_be == "": + if line == "": + next_line_must_be = None + continue + + updated_lines.append(line) + + if len(command_block) > 0: + updated_lines.extend(command_block) + + return updated_lines, modified + + +def re_on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files) -> str | None: + """ + This hook is called after the page's markdown is loaded from file and can be + """ + + def replace(match: Match): + badge_type, args = match.groups() + args = args.strip() + if badge_type == "version": + return _badge_for_version(args, page, files) + if badge_type == "scheduler": + return _badge_for_scheduler(args, page, files) + if type == "flag": + return flag(args, page, files) + + # Otherwise, raise an error + raise RuntimeError(f"Unknown shortcode: {badge_type}") + + return re.sub(r"", replace, markdown, flags=re.I | re.M) + + +def _badge(icon: str, text: str = "", badge_type: str = ""): + """ + Create badge + """ + classes = f"mdx-badge mdx-badge--{badge_type}" if badge_type else "mdx-badge" + text = f"{text}{{ data-preview='' }}" if text.endswith(")") else text + return "".join( + [ + f'', + *([f'{icon}'] if icon else []), + *([f'{text}'] if text else []), + "", + ] + ) + + +def _badge_for_scheduler(text: str, page: Page, files: Files): + """ + Create badge for scheduler + """ + + # Return badge + icon = "simple-docker" + if text in ["kubernetes", "k3s"]: + icon = "simple-kubernetes" + text = "k3s" + elif text == "nomad": + icon = "simple-nomad" + elif text in ["docker", "docker-local"]: + icon = "simple-docker" + text = "docker-local" + + href = f"https://dokku.com/docs/deployment/schedulers/{text}/" + return _badge( + icon=f"[:{icon}:]({href} '{text}')", + text=f"[{text}]({href})", + ) + + +def _badge_for_version(text: str, page: Page, files: Files): + """ + Create badge for version + """ + + # Return badge + icon = "material-tag-outline" + href = f"https://github.com/dokku/dokku/releases/tag/v{text}" + return _badge( + icon=f"[:{icon}:]({href} 'Since {text}')", + text=f"[{text}]({href})", + ) + + +def flag(args: str, page: Page, files: Files): + """ + Create flag + """ + + flag_type, *_ = args.split(" ", 1) + if flag_type == "experimental": + return _badge_for_experimental(page, files) + raise RuntimeError(f"Unknown flag type: {flag_type}") + + +def _badge_for_experimental(page: Page, files: Files): + """ + Create badge for experimental + """ + + icon = "material-flask-outline" + href = _resolve_path("conventions.md#experimental", page, files) + return _badge(icon=f"[:{icon}:]({href} 'Experimental')") + + +def _resolve_path(path: str, page: Page, files: Files): + """ + Resolve path of file relative to given page - the posixpath always includes + one additional level of `..` which we need to remove + """ + + path, anchor, *_ = f"{path}#".split("#") + path = _resolve(files.get_file_from_path(path), page) + return "#".join([path, anchor]) if anchor else path + + +def _resolve(file: File, page: Page): + """ + Resolve path of file relative to given page - the posixpath always includes + one additional level of `..` which we need to remove + """ + + path = posixpath.relpath(file.src_uri, page.file.src_uri) + return posixpath.sep.join(path.split(posixpath.sep)[1:]) diff --git a/docs/_build/requirements.txt b/docs/_build/requirements.txt new file mode 100644 index 00000000000..dfd4dafa978 --- /dev/null +++ b/docs/_build/requirements.txt @@ -0,0 +1,23 @@ +beautifulsoup4==4.15.0 +click==8.4.2 +ghp-import==2.1.0 +importlib-metadata==9.0.0 +Jinja2==3.1.6 +Markdown>=3.10.2,<3.11 +MarkupSafe==3.0.3 +mergedeep==1.3.4 +mkdocs==1.6.1 +mkdocs-exclude==1.0.2 +mkdocs-material==9.7.7 +mkdocs-material-extensions==1.3.1 +packaging==26.2 +Pygments==2.20.0 +pymdown-extensions==11.0.1 +pyparsing==3.3.2 +python-dateutil==2.9.0.post0 +PyYAML==6.0.3 +pyyaml_env_tag==1.1 +six==1.17.0 +soupsieve==2.9.1 +watchdog==6.0.0 +zipp==4.1.0 diff --git a/docs/_overrides/main.html b/docs/_overrides/main.html new file mode 100644 index 00000000000..8233afc3bce --- /dev/null +++ b/docs/_overrides/main.html @@ -0,0 +1,56 @@ + + +{% extends "base.html" %} + + +{% block extrahead %} + + + +{% endblock %} + + +{% block announce %} + + For updates follow @dokku on + + Twitter + +{% endblock %} + + +{% block scripts %} + {{ super() }} + + + +{% endblock %} + +{% block outdated %} + You're not viewing the latest version. + + Click here to go to latest. + +{% endblock %} diff --git a/docs/advanced-usage/backup-recovery.md b/docs/advanced-usage/backup-recovery.md index df487834b68..fae61cc6d6b 100644 --- a/docs/advanced-usage/backup-recovery.md +++ b/docs/advanced-usage/backup-recovery.md @@ -6,7 +6,8 @@ The best plan for disaster recovery is to always keep multiple (remote) copies o ## TLDR -> Warning: This method has many caveats. Please read this entire document before assuming these backups work as expected, and test your backups on a regular basis. +> [!WARNING] +> This method has many caveats. Please read this entire document before assuming these backups work as expected, and test your backups on a regular basis. ### Creating a backup @@ -95,6 +96,14 @@ System administrators are highly encouraged to store persistent data in app-spec See the [persistent storage documentation](/docs/advanced-usage/persistent-storage.md) for more information on how to attach persistent storage to your app. +### Restoring on a machine with a different CPU Architecture + +When restoring a backup on a machine that has a different CPU architecture, you will need to clear out the `~/.basher` as it's not portable across different CPU architectures. Do so with the following command immediately after restoring your dokku configs: + +```shell +rm -rf /home/dokku/.basher +``` + ## Recovering app code In case of an emergency when your git repo and backups are completely lost, you can recover the last pushed copy from your remote Dokku server (assuming you still have the ssh key). diff --git a/docs/advanced-usage/builds.md b/docs/advanced-usage/builds.md new file mode 100644 index 00000000000..1e80bca4308 --- /dev/null +++ b/docs/advanced-usage/builds.md @@ -0,0 +1,197 @@ +# Build Tracking + +> [!IMPORTANT] +> New as of 0.38.0 + +Every deploy that flows through Dokku - whether triggered by `git push`, `ps:rebuild`, `ps:restart`, `config:set`, or `git:from-archive` / `git:from-image` / `git:sync` / `git:load-image` - is recorded as a structured build record on disk. The `builds` plugin lets operators inspect what is currently deploying, look up the result of an old deploy, and stream the captured build log without depending on `journalctl`. + +``` +builds:cancel # Cancel a running build for an app +builds:info [--format json] # Show details for a single build +builds:list [] [--format json] [--kind ...] [--status ...] + # List builds (running across all apps, or running + history for one) +builds:output [|current] # Show build output (tail for live, cat for finished) +builds:prune [--all-apps] # Reap abandoned records and apply retention +builds:report [] [] # Display a build report +builds:set [--global|] [] # Set or clear a builds property +``` + +## Build records + +Every build is identified by a sortable base36 ULID-style id (`DOKKU_BUILD_ID`) generated at deploy start. For each build, Dokku writes: + +- `$DOKKU_LIB_ROOT/data/builds//.json` - the structured record +- `$DOKKU_LIB_ROOT/data/builds//.log` - the captured stdout/stderr of the deploy + +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. + +### Record schema + +```json +{ + "id": "01j8c4xv7bk5w3", + "app": "myapp", + "kind": "build", + "pid": 12345, + "started_at": "2026-04-30T13:50:00Z", + "finished_at": "2026-04-30T13:51:14Z", + "status": "succeeded", + "source": "git-hook", + "exit_code": 0 +} +``` + +- **kind** - `build` for paths that produce a new image (`git push`, `git:*`, `ps:rebuild`); `deploy` for paths that re-deploy an existing image (`ps:restart`, `ps:start`, `dokku deploy`, `config:set`). +- **status** - on-disk values are `running | succeeded | failed | canceled`. `abandoned` is a fifth display-only value computed at read time for `running` records whose PID is no longer alive; it is never persisted to the record. +- **source** - the user-typed command that originated the deploy (e.g. `git-hook`, `ps:restart`, `config-redeploy`, `git:sync`). + +## Listing builds + +Without an app argument, `builds:list` shows currently-running builds across every app on the host: + +```shell +dokku builds:list +``` + +``` +=====> Currently running builds +App Build ID Kind PID Source Started +myapp 01j8c4xv7bk5w3 build 12345 git-hook 2026-04-30T13:50:00Z +``` + +With an app, `builds:list` returns running builds plus the most recent finalized records up to the configured retention: + +```shell +dokku builds:list myapp +``` + +Filter by kind or status: + +```shell +dokku builds:list myapp --kind build +dokku builds:list myapp --status running +dokku builds:list --format json +``` + +## Inspecting a single build + +```shell +dokku builds:info myapp 01j8c4xv7bk5w3 +``` + +``` +=====> Build 01j8c4xv7bk5w3 + Build ID: 01j8c4xv7bk5w3 + App: myapp + Kind: build + Status: succeeded + PID: 12345 + Source: git-hook + Started: 2026-04-30T13:50:00Z + Finished: 2026-04-30T13:51:14Z + Duration: 1m14s + Exit Code: 0 + Log: /var/lib/dokku/data/builds/myapp/01j8c4xv7bk5w3.log +``` + +JSON output includes the same fields plus a computed `log_path`: + +```shell +dokku builds:info myapp 01j8c4xv7bk5w3 --format json | jq . +``` + +## Streaming build output + +```shell +# tail -f the live build, or cat the log for a finished one +dokku builds:output myapp 01j8c4xv7bk5w3 + +# resolve "current" from the in-flight deploy +dokku builds:output myapp current +``` + +If the on-disk log file is missing (for example, a build from before this plugin was installed), `builds:output` falls back to `journalctl -t dokku-`. + +## Cancelling a build + +`builds:cancel` reads the active `.deploy.lock` for an app, looks up the matching build record, and sends `SIGQUIT` to the deploy's process group. The record is finalized as `canceled` (or `failed` if the process had already exited without finalizing - in that case Dokku marks the record so it doesn't sit in `running` forever). + +```shell +dokku builds:cancel myapp +``` + +If the record is already finalized when cancel runs, no signal is sent and the record is left untouched. + +## Retention + +Build records are pruned by count, not by age. The default retention is **20** records per app. Live in-flight deploys are never pruned regardless of count. + +Set a per-app retention: + +```shell +dokku builds:set myapp retention 50 +``` + +Set the global default: + +```shell +dokku builds:set --global retention 10 +``` + +Clear an override (falls back to the global value, then the default): + +```shell +dokku builds:set myapp retention +``` + +## Manual pruning + +`builds:prune` invokes the same logic that runs at the end of every deploy - it reaps abandoned records (status=running with a dead PID, finalized as `failed`) and trims the directory to the configured retention. Useful after lowering retention via `builds:set` or to clean up after a host reboot: + +```shell +dokku builds:prune myapp +dokku builds:prune --all-apps +``` + +## Reports + +```shell +dokku builds:report +dokku builds:report myapp +dokku builds:report myapp --build-status +``` + +Available flags: + +- `--build-id`, `--build-kind`, `--build-status`, `--build-pid`, `--build-source`, `--build-started-at`, `--build-finished-at`, `--build-exit-code`: details of the most recent build for the app +- `--builds-retention`: per-app retention override (empty if none) +- `--builds-global-retention`: global retention override (empty if none) +- `--builds-computed-retention`: the resolved retention applied to this app + +`--build-status` returns the **display** status, so an abandoned in-flight build shows `abandoned` rather than `running`. The raw on-disk status is only visible by reading the JSON record directly. + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `builds:report`. The JSON keys emitted by `builds:report --format json` are the same names with the leading `--builds-` stripped (e.g. `retention`, `global-retention`, `computed-retention`). Legacy keys with the `builds-` prefix (e.g. `builds-retention`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. Status keys (`build-id`, `build-status`, etc.) have no plugin prefix and are unaffected. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `retention` | app + global | `20` | `--builds-retention`, `--builds-global-retention`, `--builds-computed-retention` | Number of recent build records kept per app; older finalized records are pruned at the end of each deploy | + +### Read-only 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 | +| `--build-source` | Trigger that started the build (e.g. `git-hook`, `ps:rebuild`, `ps:restart`) | +| `--build-pid` | PID of the build process | +| `--build-started-at` | UNIX timestamp the build started | +| `--build-finished-at` | UNIX timestamp the build finished | +| `--build-exit-code` | Exit code of the build process | diff --git a/docs/advanced-usage/deployment-tasks.md b/docs/advanced-usage/deployment-tasks.md index 6b18c7f7f93..1c278a80328 100644 --- a/docs/advanced-usage/deployment-tasks.md +++ b/docs/advanced-usage/deployment-tasks.md @@ -1,5 +1,6 @@ # Deployment Tasks +> [!IMPORTANT] > New as of 0.5.0 ## Usage @@ -17,30 +18,30 @@ To support this, Dokku provides support for a special `release` command within y Each "phase" has different expectations and limitations: - `app.json`: `scripts.dokku.predeploy` - - When to use: This should be used if your app does not support arbitrary build commands and you need to make changes to the built image. - - Are changes committed to the image at this phase: Yes - - Example use-cases - - Bundling assets in a slightly different way - - Installing a custom package from source or copying a binary into place + - When to use: This should be used if your app does not support arbitrary build commands and you need to make changes to the built image. + - Are changes committed to the image at this phase: Yes + - Example use-cases + - Bundling assets in a slightly different way + - Installing a custom package from source or copying a binary into place - `app.json`: `scripts.dokku.postdeploy` - - When to use: This should be used in conjunction with external systems to signal the completion of your deploy. - - Are changes committed to the image at this phase: No - - Example use-cases - - Notifying slack that your app is deployed - - Coordinating traffic routing with a central load balancer + - When to use: This should be used in conjunction with external systems to signal the completion of your deploy. + - Are changes committed to the image at this phase: No + - Example use-cases + - Notifying slack that your app is deployed + - Coordinating traffic routing with a central load balancer - `app.json`: `scripts.postdeploy` - - When to use: This should be used when you wish to run a command _once_, after the app is created and not on subsequent deploys to the app. - - Are changes committed to the image at this phase: No - - Example use-cases - - Setting up OAuth clients and DNS - - Loading seed/test data into the app’s test database + - When to use: This should be used when you wish to run a command _once_, after the app is created and not on subsequent deploys to the app. + - Are changes committed to the image at this phase: No + - Example use-cases + - Setting up OAuth clients and DNS + - Loading seed/test data into the app’s test database - `Procfile`: `release` - - When to use: This should be used in conjunction with external systems to signal the completion of your app image build. - - Are changes committed to the image at this phase: No - - Example use-cases - - Sending CSS, JS, and other assets from your app’s slug to a CDN or S3 bucket - - Priming or invalidating cache stores - - Running database migrations + - When to use: This should be used in conjunction with external systems to signal the completion of your app image build. + - Are changes committed to the image at this phase: No + - Example use-cases + - Sending CSS, JS, and other assets from your app’s slug to a CDN or S3 bucket + - Priming or invalidating cache stores + - Running database migrations Additionally, if using a Dockerfile with an `ENTRYPOINT`, the deployment task is passed to that entrypoint as is. The exceptions are if the entrypoint is one of the following: @@ -55,12 +56,19 @@ Please keep the above in mind when utilizing deployment tasks. ### Changing the `app.json` location -When deploying a monorepo, it may be desirable to specify the specific path of the `app.json` file to use for a given app. This can be done via the `app-json:set` command. If a value is specified and that file does not exist within the repository, Dokku will continue the build process as if the repository has no `app.json` file. +The `app.json` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `appjson-path` property: ```shell -dokku app-json:set node-js-app appjson-path second-app.json +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 default value may be set by passing an empty value for the option: ```shell @@ -81,6 +89,7 @@ dokku app-json:set --global appjson-path ### Displaying app-json reports for an app +> [!IMPORTANT] > New as of 0.25.0 You can get a report about the app's storage status using the `app-json:report` command: @@ -92,18 +101,20 @@ dokku app-json:report ``` =====> node-js-app app-json information App-json computed appjson path: app2.json - App-json global appjson path: app.json + App-json global appjson path: App-json appjson path: app2.json =====> python-sample app-json information App-json computed appjson path: app.json - App-json global appjson path: app.json + App-json global appjson path: App-json appjson path: =====> ruby-sample app-json information App-json computed appjson path: app.json - App-json global appjson path: app.json + App-json global appjson path: App-json appjson path: ``` +The `appjson-path` and `global-appjson-path` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-appjson-path` key holds the effective value used at deploy time, falling back to the global value (where one has been set) and then to the built-in default of `app.json`. + You can run the command for a specific app also. ```shell @@ -113,7 +124,7 @@ dokku app-json:report node-js-app ``` =====> node-js-app app-json information App-json computed appjson path: app2.json - App-json global appjson path: app.json + App-json global appjson path: App-json appjson path: app2.json ``` @@ -137,9 +148,8 @@ Dokku provides limited support for the `app.json` manifest from Heroku (document - `scripts.dokku.postdeploy`: This is run _after_ an app's containers are scheduled. Changes made to your image are _not_ committed at this phase. - `scripts.postdeploy`: This is run _after_ an app's containers are scheduled. Changes made to your image are _not_ committed at this phase. -For buildpack-based deployments, the location of the `app.json` file should be at the root of your repository. Dockerfile-based app deploys should have the `app.json` in the configured `WORKDIR` directory; otherwise Dokku defaults to the buildpack app behavior of looking in `/app`. - -> Warning: Any failed `app.json` deployment task will fail the deploy. In the case of either phase, a failure will not affect any running containers. +> [!WARNING] +> Any failed `app.json` deployment task will fail the deploy. In the case of either phase, a failure will not affect any running containers. The following is an example `app.json` file. Please note that only the `scripts.dokku.predeploy` and `scripts.dokku.postdeploy` tasks are supported by Dokku at this time. All other fields will be ignored and can be omitted. @@ -157,6 +167,7 @@ The following is an example `app.json` file. Please note that only the `scripts. #### Procfile Release command +> [!IMPORTANT] > New as of 0.14.0 The `Procfile` also supports a special `release` command which acts in a similar way to the [Heroku Release Phase](https://devcenter.heroku.com/articles/release-phase). This command is executed _after_ an app's docker image is built, but _before_ any containers are scheduled. This is also run _after_ any command executed by `scripts.dokku.predeploy`. @@ -169,4 +180,5 @@ release: curl https://some.external.api.service.com/deployment?state=built Unlike the `scripts.dokku.predeploy` command, changes made during by the `release` command are _not_ persisted to disk. -> Warning: scaling the release command up will likely result in unspecified issues within your deployment, and is highly discouraged. +> [!WARNING] +> scaling the release command up will likely result in unspecified issues within your deployment, and is highly discouraged. diff --git a/docs/advanced-usage/docker-options.md b/docs/advanced-usage/docker-options.md index 16e82cd3ef3..054904e34b6 100644 --- a/docs/advanced-usage/docker-options.md +++ b/docs/advanced-usage/docker-options.md @@ -1,57 +1,103 @@ # Docker Container Options +> [!IMPORTANT] > New as of 0.3.17 -Pass [container options](https://docs.docker.com/engine/reference/run/) to the `docker run` command during Dokku's `build`, `deploy` and `run` phases - ``` -docker-options:add OPTION # Add Docker option to app for phase (comma-separated phase list) -docker-options:clear [...] # Clear a docker options from application -docker-options:remove OPTION # Remove Docker option from app for phase (comma-separated phase list) -docker-options:report [] [] # Displays a docker options report for one or more apps +docker-options:add [--process PROC...] OPTION # Add Docker option to app for phase +docker-options:clear [--process PROC...] [...] # Clear docker options from app +docker-options:list [--process PROC] --phase PHASE # List docker options for one process+phase pair +docker-options:remove [--process PROC...] OPTION # Remove Docker option from app for phase +docker-options:report [] [] [--format json|stdout] # Displays a docker options report for one or more apps ``` -> When specifying multiple phases, they **must** be comma-separated _without_ spaces in between each phase, like so: -> -> ```shell -> dokku docker-options:add node-js-app deploy,run "-v /var/log/node-js-app:/app/logs" -> ``` +The `docker-options` plugin allows users to specify custom [container options](https://docs.docker.com/engine/reference/run/) for containers created by Dokku at various phases. + +## Usage + +### Background + +#### Phases + +Dokku deploys your app in multiple "phases" and the `docker-options` plugin allows you to pass arguments to their underlying docker container: -## About Dokku phases +- `build`: The `build` phase is used to provide container options that are available during the build process for use by the various builders. + - A given builder may strip out or ignore options that are unsupported by the builder in question - as an example, the `dockerfile` builder does not support mounted volumes. +- `deploy`: The `deploy` phase is used to provide container options that are set on _deployed_ process types. This covers every process type specified in an app `Procfile` as well as any default processes your app may deploy. + - The `deploy` phase is usually the correct phase to add options for running containers. +- `run`: The `run` phase is used to provide container options to one-off containers created by `dokku run`, `dokku run:detached`, and any cron tasks specified in your `app.json`. -Dokku deploys your application in multiple "phases" and the `docker-options` plugin allows you to pass arguments to their underlying docker container: +> [!IMPORTANT] +> The `run` phase does _not_ correspond 1-to-1 to `docker run` or `docker container run` commands. Specifying a container option at the `run` phase will only be invoked on containers created by the `run` plugin and cron tasks. Please be sure to specify options at the correct phase for your use-case. -- `build`: the container that executes the appropriate buildpack -- `deploy`: the container that executes your running/deployed application -- `run`: the container that executes any arbitrary command via `dokku run` +Adding or removing docker-options will not apply to any running containers, and only applies to containers created _after_ the options have been modified. As such, changing an app's docker options must be followed by a `dokku ps:rebuild` or a deploy in order to take effect. -Manipulation of docker options will not restart running containers. This enables multiple options to be set/unset before final application. As such, changing an app's docker options must be followed by a `dokku ps:rebuild` in order to take effect. +#### 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 [here](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. -## Examples +#### Mounting volumes and host directories -### Add Docker options +Docker supports volume and host directory mounting via the `-v` or `--volume` flags. In order to simplify usage, Dokku provides a `storage` plugin as an abstraction to interact with persistent storage. In most cases, the Dokku project recommends using the persistent storage plugin over directly manipulating docker options at different phases. See the [persistent storage documentation](/docs/advanced-usage/persistent-storage.md) for more information on how to attach persistent storage to your app. -Add some options for the deployed/running app and when executing [`dokku run`](/docs/processes/one-off-tasks.md): +### Commands + +#### Add Docker options + +To add a docker option to an app, use the `docker-options:add` command. This takes an app name, a comma-separated list of phases, and the docker-option to add. ```shell -# Mount a host volume in a Docker container: "-v /host/path:/container/path" -dokku docker-options:add node-js-app deploy "-v /var/log/node-js-app:/app/logs" -dokku docker-options:add node-js-app run "-v /var/log/node-js-app:/app/logs" +dokku docker-options:add node-js-app deploy "--ulimit nofile=12" ``` -> Note: When [mounting a host directory](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) in a Dokku app you should first create that directory as user `dokku` and then mount the directory under `/app` in the container using `docker-options` as above. Otherwise the app will lack write permission in the directory. +Multiple phases can be specified by using a comma when specifying phases: + +```shell +dokku docker-options:add node-js-app deploy,run "--ulimit nofile=12" +``` -### Remove a Docker option +Multiple docker options can also be specified in a single call. Each `--flag [value]` group is detected on flag boundaries, shell-tokenized for quoting safety, and stored as its own entry so it round-trips through `docker-options:report` and `docker-options:list`: ```shell -dokku docker-options:remove node-js-app run "-v /var/log/node-js-app:/app/logs" +dokku docker-options:add node-js-app deploy "--ulimit nofile=12" "--shm-size 256m" ``` -### Clear all Docker options for an app +Option values are stored and passed to the container verbatim. Quoting only controls how a value is split into words - no shell expansion is performed, so `$(...)`, backticks, `$VAR`, and globs are treated literally rather than being interpreted by the shell. This is what lets values such as a Traefik router rule be applied as-is: + +```shell +dokku docker-options:add node-js-app deploy '--label "traefik.http.routers.web.rule=Host(`node-js-app.example.com`) && PathPrefix(`/api`)"' +``` + +A misplaced `--process PROC` (i.e. one specified after the app name instead of before it) is honored as a subcommand flag rather than stored as a docker option, so the example above and the equivalent process-scoped form below behave identically: + +```shell +dokku docker-options:add --process web node-js-app deploy "--ulimit nofile=12" "--shm-size 256m" +dokku docker-options:add node-js-app deploy "--ulimit nofile=12" "--shm-size 256m" --process web +``` + +#### Remove a Docker option + +To remove docker options from an app, use the `docker-options:remove` command. This takes an app name, a comma-separated list of phases, and the docker-option to remove. + +```shell +dokku docker-options:remove node-js-app run "--ulimit nofile=12" +``` + +Multiple phases can be specified by using a comma when specifying phases: + +```shell +dokku docker-options:remove node-js-app deploy,run "--ulimit nofile=12" +``` + +Multiple docker options can also be removed in a single call, mirroring the splitting that `docker-options:add` performs: + +```shell +dokku docker-options:remove node-js-app deploy "--ulimit nofile=12" "--shm-size 256m" +``` + +#### Clear all Docker options for an app Docker options can be removed for a specific app using the `docker-options:clear` command. @@ -82,8 +128,9 @@ dokku docker-options:clear node-js-app build,run -----> Clearing docker-options for node-js-app on phase run ``` -### Displaying docker-options reports for an app +#### Displaying docker-options reports for an app +> [!IMPORTANT] > New as of 0.8.1 You can get a report about the app's docker-options status using the `docker-options:report` command: @@ -95,8 +142,8 @@ dokku docker-options:report ``` =====> node-js-app docker options information Docker options build: - Docker options deploy: -v /var/log/node-js-app:/app/logs - Docker options run: -v /var/log/node-js-app:/app/logs + Docker options deploy: --ulimit nofile=12 --shm-size 256m + Docker options run: --ulimit nofile=12 --shm-size 256m =====> python-sample docker options information Docker options build: Docker options deploy: @@ -125,3 +172,99 @@ You can pass flags which will output only the value of the specific information ```shell dokku docker-options:report node-js-app --docker-options-build ``` + +When process-specific options are configured (see below), the report exposes one additional dynamic flag per configured `process.deploy` pair, named `--docker-options-deploy.`: + +```shell +dokku docker-options:report node-js-app --docker-options-deploy.web +``` + +A machine-readable JSON view is available via `--format json`: + +```shell +dokku docker-options:report node-js-app --format json +``` + +The JSON report includes the existing string keys (`build`, `deploy`, `run`, and `deploy.` when configured) plus parallel `-list` keys for the same shorthand keys. Each `-list` value is a JSON array with one element per option as originally stored via `docker-options:add`, which allows export tooling to round-trip options that contain spaces without splitting the legacy space-joined strings. Empty phases emit an empty array (`[]`). The deprecated `docker-options-*` prefixed keys are unchanged and do not gain `-list` companions. + +```json +{ + "build": "", + "build-list": [], + "deploy": "-v /logs:/logs --memory=512m", + "deploy-list": ["-v /logs:/logs", "--memory=512m"], + "run": "", + "run-list": [], + "deploy.web": "-p 8080:5000", + "deploy.web-list": ["-p 8080:5000"], + "docker-options-build": "", + "docker-options-deploy": "-v /logs:/logs --memory=512m", + "docker-options-run": "", + "docker-options-deploy.web": "-p 8080:5000" +} +``` + +### Process-Specific Options + +> [!IMPORTANT] +> New as of 0.38.0 + +Docker options can be scoped to specific process types declared in the app's `Procfile` by passing one or more `--process` flags. This is useful when a deploy-phase option (for example a port mapping) makes sense for one process type but would conflict with another - the canonical case being a `web` process that needs `-p 6789:5000` published while the `worker` process must not bind that port. + +Process scoping is supported only for the `deploy` phase. The `build` phase runs once per app and the `run` phase covers ad-hoc commands and cron tasks where no Procfile process type is in play; both reject `--process`. + +There is no `--global` flag. Omitting `--process` keeps the historical behavior of applying the option to every container in the app. Avoiding a `--global` flag here is intentional: elsewhere in Dokku `--global` means "across all apps" (e.g. `dokku config:set --global`), which would be misleading in this plugin where the scope is always one app. + +#### Setting process-specific options + +```shell +# Add a port mapping only to the web process +dokku docker-options:add --process web node-js-app deploy "-p 6789:5000" + +# Add a GPU mount only to the worker process +dokku docker-options:add --process worker node-js-app deploy "--gpus all" +``` + +Multiple `--process` flags can be combined to apply the same option to several process types in one call: + +```shell +dokku docker-options:add --process web --process api node-js-app deploy "-v /shared:/shared" +``` + +If `--process` names a process type that is not currently declared in the app's `Procfile`, the command succeeds but emits a warning. This allows configuring options ahead of a deploy that adds the new process type. + +The `_default_` value is reserved internally and cannot be passed to `--process`. + +#### Removing and clearing process-specific options + +```shell +# Remove a single option from one process +dokku docker-options:remove --process web node-js-app deploy "-p 6789:5000" + +# Clear every option for a process+phase +dokku docker-options:clear --process worker node-js-app deploy +``` + +Without `--process`, `:remove` and `:clear` operate on the default scope only - per-process lists are left untouched. + +#### Listing options for a process and phase + +The `docker-options:list` command prints the options stored for a single process+phase pair, one option per line. Omitting `--process` lists the default scope. + +```shell +dokku docker-options:list node-js-app --process web --phase deploy +dokku docker-options:list node-js-app --phase deploy +``` + +## Properties + +### Internal properties + +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 | +| `migrated-run` | per-app | Per-app marker recording that the app's legacy `DOCKER_OPTIONS_RUN` file was drained into the `_default_.run` 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/event-logs.md b/docs/advanced-usage/event-logs.md index 645b6f77f0c..68b8336a5ca 100644 --- a/docs/advanced-usage/event-logs.md +++ b/docs/advanced-usage/event-logs.md @@ -1,5 +1,6 @@ # Dokku Event Logs +> [!IMPORTANT] > New as of 0.3.21 Docker provides an _events_ command to show system's real time events. Likewise, Dokku can record events as syslog entries and also provides a plugin to display the last ones. @@ -33,7 +34,6 @@ dokku events Jul 3 16:09:48 dokku.me dokku[127630]: INVOKED: pre-release-buildpack( pythonapp ) Jul 3 16:10:02 dokku.me dokku[128095]: INVOKED: docker-args-run( rubyapp ) Jul 3 16:10:02 dokku.me dokku[128114]: INVOKED: docker-args-run( nhl ) -Jul 3 16:10:03 dokku.me dokku[128136]: INVOKED: post-release-buildpack( pythonapp ) Jul 3 16:10:03 dokku.me dokku[128195]: INVOKED: pre-deploy( pythonapp ) Jul 3 16:10:23 dokku.me dokku[129253]: INVOKED: docker-args-deploy( pythonapp ) Jul 3 16:10:24 dokku.me dokku[129451]: INVOKED: check-deploy( pythonapp 6274ced0d4be11af4490cd18abaf77cdd593f025133f403d984e80d86a39acec web 5000 10.0.16.80 ) @@ -68,8 +68,6 @@ post-build-dockerfile post-delete post-deploy post-domains-update -post-release-buildpack -post-release-dockerfile pre-build-buildpack pre-build-dockerfile pre-delete @@ -78,4 +76,4 @@ pre-release-buildpack pre-release-dockerfile receive-app update -``` \ No newline at end of file +``` diff --git a/docs/advanced-usage/persistent-storage.md b/docs/advanced-usage/persistent-storage.md index 1edc4d40480..2dc09dc250e 100644 --- a/docs/advanced-usage/persistent-storage.md +++ b/docs/advanced-usage/persistent-storage.md @@ -1,38 +1,96 @@ # Persistent Storage -> New as of 0.5.0 +> [!IMPORTANT] +> New as of 0.5.0. Named storage entries new as of 0.38.0. -The preferred method to mount external containers to a Dokku managed container, is to use the Dokku storage plugin. +The preferred method to attach persistent storage to a Dokku-managed container is the Dokku storage plugin. ``` -storage:ensure-directory [--chown option] # Creates a persistent storage directory in the recommended storage path -storage:list # List bind mounts for app's container(s) (host:container) -storage:mount # Create a new bind mount -storage:report [] [] # Displays a checks report for one or more apps -storage:unmount # Remove an existing bind mount +storage:create [] [flags] # Register a named storage entry +storage:destroy [--force] # 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: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 [flags] # Update a storage entry in place +storage:unmount [--container-dir ] # Remove an attachment +storage:wait # Block until a k3s entry's PVC is bound ``` -> The storage plugin is compatible with storage mounts created with the docker-options. The storage plugin will only list mounts from the deploy/run phase. +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. + +The legacy `storage:mount :` form continues to work on docker-local. On a k3s app it is rejected; create a named entry with `storage:create --scheduler k3s` and mount it instead. Existing colon-form mounts are migrated automatically the first time the new code runs - they show up as `legacy-` entries in `storage:list-entries`. The storage plugin supports the following mount points: -- explicit paths that exist on the host -- docker volumes +- explicit paths that exist on the host (docker-local) +- docker volumes (docker-local) +- PersistentVolumeClaims provisioned via a StorageClass (k3s) +- hostPath-backed PVs (k3s) ## Usage +### Listing persistent storage + +Persistent storage bind mounts are specified on a per-app basis, and can be listed with the `storage:list` command: + +```shell +dokku storage:list node-js-app +``` + +``` +-----> node-js-app volume bind-mounts: + /var/lib/dokku/data/storage/node-js-app:/app/storage +``` + +The output format can also be set to `json` for programmatic access: + +```shell +dokku storage:list node-js-app --format json +``` + +``` +[ + { + "entry_name": "node-js-app", + "host_path": "/var/lib/dokku/data/storage/node-js-app", + "container_path": "/app/storage" + } +] +``` + +Each entry mirrors the underlying attachment. `readonly` (boolean) and `volume_options` (string) reflect `Attachment.Readonly` and `Attachment.VolumeOptions` directly and are only present when set, so external tooling can drift-detect attachments against the raw attachment fields. For example, a mount created with `--volume-options noexec,nosuid --volume-readonly` renders as: + +``` +[ + { + "entry_name": "node-js-data", + "host_path": "/var/lib/dokku/data/storage/node-js-data", + "container_path": "/app/storage", + "readonly": true, + "volume_options": "noexec,nosuid" + } +] +``` + ### Creating storage directories +> [!IMPORTANT] > New as of 0.25.5 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. ```shell -dokku storage:ensure-directory lollipop +dokku storage:ensure-directory node-js-app ``` ``` ------> Ensuring /var/lib/dokku/data/storage/lollipop exists +-----> Ensuring /var/lib/dokku/data/storage/node-js-app exists Setting directory ownership to 32767:32767 Directory ready for mounting ``` @@ -40,16 +98,23 @@ dokku storage:ensure-directory lollipop By default, permissions are set for usage with Herokuish buildpacks. These permissions can be changed via the `--chown` option according to the following table: - `--chown herokuish` (default): Use `32767:32767` as the folder permissions. - - This is used for apps deployed with Buildpacks via Herokuish. + - This is used for apps deployed with Buildpacks via Herokuish. - `--chown heroku`: Use `1000:1000` as the folder permissions. - - This is used for apps deployed with Cloud Native Buildpacks using the `heroku/buildpacks` builder. -- `--chown packeto`: Use `2000:2000` as the folder permissions. - - This is used for apps deployed with Cloud Native Buildpacks using the `cloudfoundry/cnb` or `packeto` builders. + - This is used for apps deployed with Cloud Native Buildpacks using the `heroku/builder` builder. +- `--chown paketo`: Use `2000:2000` as the folder permissions. + - This is used for apps deployed with Cloud Native Buildpacks using the `cloudfoundry/cnb` or `paketo` builders. +- `--chown root`: Use `0:0` as the folder permissions. + - This is used for containers that run their processes as root, as is typical for most Dockerfile or Docker image deploys. - `--chown false`: Skips the `chown` call. +- `--chown `: Use `:` as the folder permissions, where `` is a custom numeric user/group id. + - This is used for containers that run their processes as a uid/gid that doesn't correspond to any of the above named options. Users deploying via Dockerfile will want to specify `--chown false` and manually `chown` the created directory if the user and/or group id of the runnning process in the deployed container do not correspond to any of the above options. -> Warning: Failing to set the correct directory ownership may result in issues in persisting files written to the mounted storage directory. +The `--chown` flag - whether on `storage:create` or `storage:ensure-directory` - only manages the default `/var/lib/dokku/data/storage/` location. If a custom `` is passed to `storage:create`, the chown call is refused and the operator must chown the path themselves. + +> [!WARNING] +> Failing to set the correct directory ownership may result in issues in persisting files written to the mounted storage directory. ### Mounting storage into apps @@ -69,6 +134,17 @@ In the first example, Dokku will then mount the shared contents of `/var/lib/dok > 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. +For named storage entries, additional Docker mount options can be passed via `--volume-options`. The value is a comma-separated mount-options string stored verbatim on the attachment and rendered into the `-v` flag at deploy time. This is useful for SELinux labels (`Z`, `z`) or hardening flags (`noexec,nosuid`): + +```shell +dokku storage:create node-js-data +dokku storage:mount node-js-app node-js-data --container-dir /app/storage --volume-options Z +``` + +When combined with `--volume-readonly`, the rendered options become `ro,` - for example, `--volume-options noexec,nosuid --volume-readonly` renders as `:ro,noexec,nosuid`. + +Re-running `storage:mount` against a named entry with the same `--container-dir` and `--process-type` updates the existing attachment's mount-time attributes (`--phase`, `--volume-subpath`, `--volume-readonly`, `--volume-chown`, `--volume-options`) in place rather than appending a duplicate. This is the idempotent equivalent of `storage:set` for entries, and lets declarative tooling change a mount-time attribute without an unmount-then-remount dance that would briefly drop the volume from `storage:report`. Mount-time fields are rewritten wholesale, not merged - omitting a flag on a re-mount clears any previously-set value. The legacy `host:container[:opts]` form still rejects duplicates with `Mount path already exists.`. + Once persistent storage is mounted, the app requires a restart. See the [process scaling documentation](/docs/processes/process-management.md) for more information. ```shell @@ -93,8 +169,39 @@ Once persistent storage is unmounted, the app requires a restart. See the [proce dokku ps:restart app-name ``` +### Destroying storage entries + +A named storage entry can be removed with the `storage:destroy` command. The entry must first be unmounted from every app that mounts it. + +```shell +dokku storage:destroy rdmtest-entry +``` + +As the command is destructive - removing the registry entry and, depending on the scheduler and reclaim policy, the underlying volume - it will default to asking for confirmation before executing the removal. + +``` + ! WARNING: Potentially Destructive Action + ! This command will destroy storage entry rdmtest-entry. + ! To proceed, type "rdmtest-entry" +> rdmtest-entry +-----> Storage entry rdmtest-entry destroyed +``` + +The confirmation may be avoided by providing the `--force` flag, which is useful for non-interactive or automated callers: + +```shell +dokku storage:destroy rdmtest-entry --force +``` + +The global `--force` flag is also supported: + +```shell +dokku --force storage:destroy rdmtest-entry +``` + ### Displaying storage reports for an app +> [!IMPORTANT] > New as of 0.8.1 You can get a report about the app's storage status using the `storage:report` command: @@ -137,6 +244,60 @@ You can pass flags which will output only the value of the specific information dokku storage:report node-js-app --storage-deploy-mounts ``` +In addition to the aggregated `Storage build/deploy/run mounts:` lines, the report emits one flat dotted key per attachment field, indexed from `1`. The key shape is `--storage-attachment..` for each of `entry-name`, `host-path`, `container-path`, `phases`, `process-type`, `subpath`, `readonly`, `volume-options`, and `volume-chown`. Fields render as empty strings when unset, and attachments are ordered by lex-sort of the index (so `10` sorts before `2`): + +```shell +dokku storage:create node-js-data +dokku storage:mount node-js-app node-js-data --container-dir /app/storage --volume-options Z --volume-chown herokuish --volume-subpath uploads +dokku storage:report node-js-app +``` + +``` +=====> node-js-app storage information + Storage attachment 1 container path: /app/storage + Storage attachment 1 entry name: node-js-data + Storage attachment 1 host path: /var/lib/dokku/data/storage/node-js-data + Storage attachment 1 phases: deploy,run + Storage attachment 1 process type: _default_ + Storage attachment 1 readonly: false + Storage attachment 1 subpath: uploads + Storage attachment 1 volume chown: herokuish + Storage attachment 1 volume options: Z + Storage build mounts: + Storage deploy mounts: -v /var/lib/dokku/data/storage/node-js-data:/app/storage:Z + Storage run mounts: -v /var/lib/dokku/data/storage/node-js-data:/app/storage:Z +``` + +The same keys are exposed in JSON output, both in the stripped (`attachment.1.volume-options`) and legacy (`storage-attachment.1.volume-options`) forms: + +```shell +dokku storage:report node-js-app --format json | jq '. | with_entries(select(.key | startswith("attachment.")))' +``` + +```json +{ + "attachment.1.container-path": "/app/storage", + "attachment.1.entry-name": "node-js-data", + "attachment.1.host-path": "/var/lib/dokku/data/storage/node-js-data", + "attachment.1.phases": "deploy,run", + "attachment.1.process-type": "_default_", + "attachment.1.readonly": "false", + "attachment.1.subpath": "uploads", + "attachment.1.volume-chown": "herokuish", + "attachment.1.volume-options": "Z" +} +``` + +A single attachment field can be fetched directly via the info-flag form: + +```shell +dokku storage:report node-js-app --storage-attachment.1.volume-options +``` + +``` +Z +``` + ## Use Cases ### Sharing storage across deploys @@ -165,14 +326,27 @@ dokku docker-options:add node-js-app build "-v /tmp/python-test:/opt/test" You cannot use mounted volumes during the build phase of a Dockerfile deploy. This is because Docker does not support volumes when executing `docker build`. -> Note: **This can cause data loss** if you bind a mount under `/app` in buildpack apps as herokuish will attempt to remove the original app path during the build phase. +> [!WARNING] +> **This can cause data loss** if you bind a mount under `/app` in buildpack apps as herokuish will attempt to remove the original app path during the build phase. ## App User and Persistent Storage file ownership (buildpack apps only) +> [!IMPORTANT] > New as of 0.7.1 By default, Dokku will execute your buildpack app processes as the `herokuishuser` user. You may override this by setting the `DOKKU_APP_USER` config variable. -> NOTE: this user must exist in your herokuish image. +> [!NOTE] +> this user must exist in your herokuish image. + +Additionally, the default `docker-local` scheduler that comes with Dokku will ensure your storage mounts are owned by either `herokuishuser` or the overridden value you have set in `DOKKU_APP_USER`. See the [docker-local scheduler documentation](/docs/deployment/schedulers/docker-local.md#disabling-chown-of-persistent-storage) docs for more information. + +## Properties + +### Internal properties + +The following property is recorded internally by the storage plugin and is not exposed via `storage:report`: -Additionally, the default `docker-local` scheduler that comes with Dokku will ensure your storage mounts are owned by either `herokuishuser` or the overridden value you have set in `DOKKU_APP_USER`. See the [docker-local scheduler documentation](/docs/advanced-usage/schedulers/docker-local.md#disabling-chown-of-persistent-storage) docs for more information. +| Property | Scope | Description | Source | +|---|---|---|---| +| `legacy-mounts-migrated` | per-app | Per-app marker recording that the app's legacy `-v` docker-options entries were drained into named storage entries plus attachments. Only set when at least one `-v` line was actually migrated; apps that have never had legacy mounts never receive this marker | `plugins/storage/migrate.go` writes `"true"` after a successful drain | diff --git a/docs/advanced-usage/plugin-management.md b/docs/advanced-usage/plugin-management.md index f736c238c4f..0f0e1b1b415 100644 --- a/docs/advanced-usage/plugin-management.md +++ b/docs/advanced-usage/plugin-management.md @@ -1,17 +1,18 @@ # Plugin Management +> [!IMPORTANT] > New as of 0.4.0 ``` plugin:disable # Disable an installed plugin (third-party only) plugin:enable # Enable a previously disabled plugin -plugin:install [--core|git-url [--committish tag|branch|commit|--name custom-plugin-name]] # Optionally download git-url (with custom tag/committish) & run install trigger for active plugins (or only core ones) +plugin:install [--core|git-url] [--committish branch|commit|tag] [--name custom-plugin-name] [--skip-install-trigger] # Optionally download git-url (and pin to the specified branch/commit/tag) & run install trigger for active plugins (or only core ones) plugin:installed # Checks if a plugin is installed plugin:install-dependencies [--core] # Run install-dependencies trigger for active plugins (or only core ones) -plugin:list # Print active plugins +plugin:list [--format stdout|json] # Print active plugins plugin:trigger . # Trigger an arbitrary plugin hook plugin:uninstall # Uninstall a plugin (third-party only) -plugin:update [name [committish]] # Optionally update named plugin from git (with custom tag/committish) & run update trigger for active plugins +plugin:update [name [branch|commit|tag]] # Optionally update named plugin from git (and pin to the specified branch/commit/tag) & run update trigger for active plugins ``` ```shell @@ -35,38 +36,72 @@ dokku plugin:list ``` plugn: dev - 00_dokku-standard 0.26.8 enabled dokku core standard plugin - 20_events 0.26.8 enabled dokku core events logging plugin - app-json 0.26.8 enabled dokku core app-json plugin - apps 0.26.8 enabled dokku core apps plugin - build-env 0.26.8 enabled dokku core build-env plugin - buildpacks 0.26.8 enabled dokku core buildpacks plugin - certs 0.26.8 enabled dokku core certificate management plugin - checks 0.26.8 enabled dokku core checks plugin - common 0.26.8 enabled dokku core common plugin - config 0.26.8 enabled dokku core config plugin - docker-options 0.26.8 enabled dokku core docker-options plugin - domains 0.26.8 enabled dokku core domains plugin - enter 0.26.8 enabled dokku core enter plugin - git 0.26.8 enabled dokku core git plugin - logs 0.26.8 enabled dokku core logs plugin - network 0.26.8 enabled dokku core network plugin - nginx-vhosts 0.26.8 enabled dokku core nginx-vhosts plugin - plugin 0.26.8 enabled dokku core plugin plugin - proxy 0.26.8 enabled dokku core proxy plugin - ps 0.26.8 enabled dokku core ps plugin - repo 0.26.8 enabled dokku core repo plugin - resource 0.26.8 enabled dokku core resource plugin - scheduler-docker-local 0.26.8 enabled dokku core scheduler-docker-local plugin - shell 0.26.8 enabled dokku core shell plugin - ssh-keys 0.26.8 enabled dokku core ssh-keys plugin - storage 0.26.8 enabled dokku core storage plugin - tags 0.26.8 enabled dokku core tags plugin - tar 0.26.8 enabled dokku core tar plugin - trace 0.26.8 enabled dokku core trace plugin -``` - -> Warning: All plugin commands other than `plugin:list` and `plugin:help` require sudo access and must be run directly from the Dokku server. + 00_dokku-standard 0.38.25 enabled dokku core standard plugin + 20_events 0.38.25 enabled dokku core events logging plugin + app-json 0.38.25 enabled dokku core app-json plugin + apps 0.38.25 enabled dokku core apps plugin + build-env 0.38.25 enabled dokku core build-env plugin + buildpacks 0.38.25 enabled dokku core buildpacks plugin + certs 0.38.25 enabled dokku core certificate management plugin + checks 0.38.25 enabled dokku core checks plugin + common 0.38.25 enabled dokku core common plugin + config 0.38.25 enabled dokku core config plugin + docker-options 0.38.25 enabled dokku core docker-options plugin + domains 0.38.25 enabled dokku core domains plugin + enter 0.38.25 enabled dokku core enter plugin + git 0.38.25 enabled dokku core git plugin + logs 0.38.25 enabled dokku core logs plugin + network 0.38.25 enabled dokku core network plugin + nginx-vhosts 0.38.25 enabled dokku core nginx-vhosts plugin + plugin 0.38.25 enabled dokku core plugin plugin + proxy 0.38.25 enabled dokku core proxy plugin + ps 0.38.25 enabled dokku core ps plugin + repo 0.38.25 enabled dokku core repo plugin + resource 0.38.25 enabled dokku core resource plugin + scheduler-docker-local 0.38.25 enabled dokku core scheduler-docker-local plugin + shell 0.38.25 enabled dokku core shell plugin + ssh-keys 0.38.25 enabled dokku core ssh-keys plugin + storage 0.38.25 enabled dokku core storage plugin + tags 0.38.25 enabled dokku core tags plugin + tar 0.38.25 enabled dokku core tar plugin + trace 0.38.25 enabled dokku core trace plugin +``` + +The list can also be emitted as JSON via the `--format json` flag. In addition to the name, version, enabled state, and description shown in the default output, the JSON output includes whether a plugin is a core plugin and - for git-based third-party plugins - the install source (the git remote URL, the currently checked-out commit, and the followed branch). This is useful for tooling that reconstructs a server's set of installed plugins: + +```shell +dokku plugin:list --format json +``` + +```json +[ + { + "name": "apps", + "version": "0.38.25", + "enabled": true, + "core": true, + "description": "dokku core apps plugin", + "source_url": "", + "committish": "", + "branch": "" + }, + { + "name": "postgres", + "version": "1.42.0", + "enabled": true, + "core": false, + "description": "dokku postgres service plugin", + "source_url": "https://github.com/dokku/dokku-postgres.git", + "committish": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0", + "branch": "master" + } +] +``` + +The `source_url`, `committish`, and `branch` fields are only populated for plugins installed from a git repository. They are empty for core plugins as well as for plugins installed from a tarball or a local `file://` path. When a plugin is pinned to a specific commit or tag (a detached checkout), the `branch` field is empty while `committish` still reports the exact commit. + +> [!WARNING] +> All plugin commands other than `plugin:list` and `plugin:help` require sudo access and must be run directly from the Dokku server. ### Checking if a plugin is installed @@ -78,6 +113,9 @@ dokku plugin:installed postgres ### Installing a plugin +> [!NOTE] +> Plugins installed in this method within a [docker-based installation](/docs/getting-started/install/docker.md) of Dokku do not persist across installs. Please see the [plugin installation section](/docs/getting-started/install/docker.md#plugin-installation) of the docker-based installation docs for further details. + Installing a plugin is easy as well using the `plugin:install` command. This command will also trigger the `install` pluginhook on all existing plugins. The most common usage is to install a plugin from a url. This url may be any of the following: @@ -123,6 +161,12 @@ The `--core` flag may also be indicated as the sole argument, though it is only dokku plugin:install --core ``` +If installing plugins in a Dockerfile, you will want to skip the `install` trigger. This will be run on container boot. + +```shell +dokku plugin:install https://github.com/dokku/smoke-test-plugin.git --name smoke-test-plugin --skip-install-trigger +``` + Finally, all flags may be omitted to trigger the `install` procedures for both core and third-party plugins: ```shell @@ -163,6 +207,12 @@ An optional commit SHA-like object may be specified. dokku plugin:update postgres 2.0.0 ``` +Any future invocation of `plugin:update` will respect the previously specified SHA-like object. To follow a particular branch again, specify that branch: + +```shell +dokku plugin:update postgres main +``` + ### Uninstalling a plugin Third party plugins can be uninstalled using the `plugin:uninstall` command: diff --git a/docs/advanced-usage/registry-management.md b/docs/advanced-usage/registry-management.md index c7882b760b8..c483eff37b7 100644 --- a/docs/advanced-usage/registry-management.md +++ b/docs/advanced-usage/registry-management.md @@ -1,11 +1,13 @@ # Registry Management +> [!IMPORTANT] > New as of 0.25.0 ``` -registry:login [--password-stdin] [] # Login to a docker registry -registry:report [] [] # Displays a registry report for one or more apps -registry:set () # Set or clear a registry property for an app +registry:login [--global|--password-stdin] [] [] # Login to a docker registry +registry:logout [--global] [] # Logout from a docker registry +registry:report [] [] # Displays a registry report for one or more apps +registry:set |--global () # Set or clear a registry property for an app ``` The registry plugin enables interacting with remote registries, which is useful when either deploying images via `git:from-image` or when interacting with custom schedulers to deploy built image artifacts. @@ -14,34 +16,75 @@ The registry plugin enables interacting with remote registries, which is useful ### Logging into a registry -The `registry:login` command can be used to log into a docker registry. The following are examples for logging into various common registries: +The `registry:login` command can be used to log into a docker registry. Credentials can be stored globally (for all apps) or on a per-app basis. + +#### Global login + +To log in globally (credentials shared by all apps), use the `--global` flag: ```shell # hub.docker.com -dokku registry:login docker.io $USERNAME $PASSWORD +dokku registry:login --global docker.io $USERNAME $PASSWORD # digitalocean # the username and password are both defined as the same api token -dokku registry:login registry.digitalocean.com $DIGITALOCEAN_API_TOKEN $DIGITALOCEAN_API_TOKEN +dokku registry:login --global registry.digitalocean.com $DIGITALOCEAN_API_TOKEN $DIGITALOCEAN_API_TOKEN # github container registry # see the following link for information on retrieving a personal access token # https://docs.github.com/en/packages/guides/pushing-and-pulling-docker-images#authenticating-to-github-container-registry -dokku registry:login ghcr.io $USERNAME $REGISTRY_PAT_TOKEN +dokku registry:login --global ghcr.io $USERNAME $REGISTRY_PAT_TOKEN # quay # a robot user may be used to login -dokku registry:login quay.io $USERNAME $PASSWORD +dokku registry:login --global quay.io $USERNAME $PASSWORD +``` + +> [!NOTE] +> For backwards compatibility, if the `--global` flag is omitted and only three arguments are provided (server, username, password), the command will behave as a global login but will show a deprecation warning. + +#### Per-app login + +To log in for a specific app, specify the app name as the first argument: + +```shell +# log into docker.io for a specific app +dokku registry:login node-js-app docker.io $USERNAME $PASSWORD + +# log into ghcr.io for a specific app +dokku registry:login node-js-app ghcr.io $USERNAME $REGISTRY_PAT_TOKEN ``` -For security reasons, the password may also be specified as stdin by specifying the `--password-stdin` flag. This is supported regardless of the registry being logged into. +Per-app credentials are stored in `/var/lib/dokku/config/registry/$APP/config.json` and are automatically used for docker operations (build, push, pull) for that specific app. + +#### Password via stdin + +For security reasons, the password may also be specified as stdin by specifying the `--password-stdin` flag. This is supported for both global and per-app logins: ```shell -echo "$PASSWORD" | dokku registry:login --password-stdin docker.io $USERNAME +# global login via stdin +echo "$PASSWORD" | dokku registry:login --global --password-stdin docker.io $USERNAME + +# per-app login via stdin +echo "$PASSWORD" | dokku registry:login node-js-app --password-stdin docker.io $USERNAME ``` For certain Docker registries - such as Amazon ECR or Google's GCR registries - users may instead wish to use a docker credential helper to automatically authenticate against a server; please see the documentation regarding the credential helper in question for further setup instructions. +### Logging out from a registry + +The `registry:logout` command can be used to log out from a docker registry: + +```shell +# global logout +dokku registry:logout --global docker.io + +# per-app logout +dokku registry:logout node-js-app docker.io +``` + +When an app is destroyed, any per-app registry credentials are automatically removed. + ### Setting a remote server To specify a remote server registry for pushes, set the `server` property via the `registry:set` command. The default value for this property is empty string. Setting the value to `docker.io` or `hub.docker.com` will result in the computed value being empty string (as that is the default, implicit registry), while any non-zero length value will have a `/` appended to it if there is not one already. @@ -69,22 +112,22 @@ dokku registry:set --global server The following are the values that should be used for common remote servers: - Amazon Elastic Container Registry: - - value: `$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/` - - notes: The `$AWS_ACCOUNT_ID` and `$AWS_REGION` should match the values for your account and region, respectively. Additionally, an IAM profile that allows `push` access to the repository specified by `image-repo` should be attached to your Dokku server. + - value: `$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/` + - notes: The `$AWS_ACCOUNT_ID` and `$AWS_REGION` should match the values for your account and region, respectively. Additionally, an IAM profile that allows `push` access to the repository specified by `image-repo` should be attached to your Dokku server. - Azure Container Registry: - - value `$REGISTRY_NAME.azurecr.io/` - - notes: The `$AKS_REGISTRY_NAME` should match the name of the registry created on your account. + - value `$REGISTRY_NAME.azurecr.io/` + - notes: The `$AKS_REGISTRY_NAME` should match the name of the registry created on your account. - Docker Hub: - - value: `docker.io/` - - notes: Requires owning the namespace used in the `image-repo` value. + - value: `docker.io/` + - notes: Requires owning the namespace used in the `image-repo` value. - Digitalocean: - - value: `registry.digitalocean.com/` - - notes: Requires setting the correct `image-repo` value for your registry. + - value: `registry.digitalocean.com/` + - notes: Requires setting the correct `image-repo` value for your registry. - Github Container Registry: - - value: `ghcr.io/` - - notes: Requires that the authenticated user has access to the namespace used in the `image-repo` value. + - value: `ghcr.io/` + - notes: Requires that the authenticated user has access to the namespace used in the `image-repo` value. - Quay.io: - - value: `quay.io/` + - value: `quay.io/` ### Specifying an image repository name @@ -98,7 +141,31 @@ Setting the property value to an empty string will reset the value to the system ```shell # per-app -dokku registry:set node-js-app push-on-release +dokku registry:set node-js-app image-repo +``` + +### Templating the image repository name + +Instead of setting the image repository name on a per-app basis, it can be set via a template with the `image-repo-template` property. The property can be set globally or for a specific app, with the per-app value overriding the global value when both are present: + +```shell +# globally +dokku registry:set --global image-repo-template "my-awesome-prefix/{{ .AppName }}" + +# per-app +dokku registry:set node-js-app image-repo-template "my-awesome-prefix/{{ .AppName }}-prod" +``` + +Dokku uses a Golang template and has access to the `AppName` variable as shown above. The per-app `image-repo` property always takes precedence over the rendered template when both are set. + +Setting the property value to an empty string will reset the value to the system default. Resetting the value can be done per app or globally. + +```shell +# per-app +dokku registry:set node-js-app image-repo-template + +# globally +dokku registry:set --global image-repo-template ``` ### Pushing images on build @@ -124,3 +191,53 @@ dokku registry:set node-js-app push-on-release # globally dokku registry:set --global push-on-release ``` + +#### Recovering from local image GC + +When `push-on-release` is enabled, Dokku treats the remote registry as the canonical store for app images. If a local image disappears - for example because a `docker image prune` cron ran, the host rebooted, or an operator removed it manually - subsequent commands that need the image (`ps:restart`, `ps:scale`, `dokku run`, `domains:add`, `certs:add`, etc.) will pull the missing tag back from the registry automatically. Per-app registry credentials configured via `registry:login` are honored during the pull. This recovery covers the deployed numeric tag pushed by the registry plugin - a missing `latest` tag will be ignored. + +### Push extra tags + +To push the image on release with extra tags, set the `push-extra-tags` to a comma-separated list of tags via the `registry:set` command. The default value for this property is empty. Setting the property will result in the image being tagged with extra tags every release. + +```shell +# multiple-tags +dokku registry:set node-js-app push-extra-tags foo,bar +``` +The `push-extra-tags` can be set to a single tag too. + +```shell +# single tag +dokku registry:set node-js-app push-extra-tags foo +``` + +This property can be set for a single app or globally via the `--global` flag. When set globally, the app-specific value will always overide the global value. The default global value for this property is `false`. + +```shell +dokku registry:set --global push-extra-tags foo,bar +``` + +Setting the property value to an empty string will reset the value to the system default. Resetting the value can be done per app or globally. + +```shell +# per-app +dokku registry:set node-js-app push-extra-tags + +# globally +dokku registry:set --global push-extra-tags +``` + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `registry:report`. The JSON keys emitted by `registry:report --format json` are the same names with the leading `--registry-` stripped (e.g. `image-repo`, `global-server`, `computed-push-on-release`). Legacy keys with the `registry-` prefix (e.g. `registry-image-repo`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `image-repo` | app only | `dokku/` | `--registry-image-repo`, `--registry-computed-image-repo` | Repository name used when pushing the app's image (overrides the global template) | +| `image-repo-template` | app + global | none | `--registry-image-repo-template`, `--registry-global-image-repo-template`, `--registry-computed-image-repo-template` | Go template used to compute the per-app image repository when `image-repo` is unset | +| `push-extra-tags` | app + global | none | `--registry-push-extra-tags`, `--registry-global-push-extra-tags`, `--registry-computed-push-extra-tags` | Comma-separated list of additional tags pushed alongside the deploy tag | +| `push-on-release` | app + global | `false` | `--registry-push-on-release`, `--registry-global-push-on-release`, `--registry-computed-push-on-release` | When `true`, pushes the image to the registry on every successful build | +| `server` | app + global | none | `--registry-server`, `--registry-global-server`, `--registry-computed-server` | Registry server host (e.g. `ghcr.io`) used when pushing images | diff --git a/docs/advanced-usage/repository-management.md b/docs/advanced-usage/repository-management.md index e8235f86326..5e1c67ccfcb 100644 --- a/docs/advanced-usage/repository-management.md +++ b/docs/advanced-usage/repository-management.md @@ -1,5 +1,6 @@ # Repository Management +> [!IMPORTANT] > New as of 0.6.0 ``` diff --git a/docs/advanced-usage/resource-management.md b/docs/advanced-usage/resource-management.md index 24ebaf7356c..804397489a1 100644 --- a/docs/advanced-usage/resource-management.md +++ b/docs/advanced-usage/resource-management.md @@ -1,5 +1,6 @@ # Resource Management +> [!IMPORTANT] > New as of 0.15.0 ``` @@ -16,7 +17,8 @@ The resource plugin is meant to allow users to limit or reserve resources for a By default, Dokku allows unlimited resource access to apps deployed on a server. In some cases, it may be desirable to limit this on a per-app or per-process-type basis. The `resource` plugin allows management of both resource "limits" and resource "reservations", where each resource request type has specific meaning to the scheduler in use for a given app. -> Warning: The meaning of a values and it's units are specific to the scheduler in use for a given app. If a value is incorrect for a scheduler, this may result in containers failing to start correctly. If a scheduler does not support a given resource type combination, it will be ignored. All resource commands require an app rebuild or deploy in order to take effect. +> [!WARNING] +> The meaning of a values and it's units are specific to the scheduler in use for a given app. If a value is incorrect for a scheduler, this may result in containers failing to start correctly. If a scheduler does not support a given resource type combination, it will be ignored. All resource commands require an app rebuild or deploy in order to take effect. Valid resource options include: @@ -28,9 +30,9 @@ Valid resource options include: - `--network-egress` - `--nvidia-gpu` -See the [Supported Resource Management Properties](/docs/advanced-usage/schedulers/docker-local.md#supported-resource-management-properties) section of the docker local scheduler documentation for more information on how each resource limit maps to Docker. +See the [Supported Resource Management Properties](/docs/deployment/schedulers/docker-local.md#supported-resource-management-properties) section of the docker local scheduler documentation for more information on how each resource limit maps to Docker. -Resource limits and reservations are applied only during the `run` and `deploy` phases of an application, and will not impact the `build` phase of an application. +Resource reservations are applied only during the `run` and `deploy` phases of an application. Resource limits also apply during the `build` phase when explicitly configured against the `build` process type - see [Build-time Resource Limits](#build-time-resource-limits) below. ### Resource Limits @@ -118,6 +120,40 @@ dokku resource:limit --process-type web node-js-app nvidia-gpu: ``` +#### Build-time Resource Limits + +> [!IMPORTANT] +> New as of 0.38.0 + +Resource limits may be applied to the build container by using the special `build` process type. This allows constraining memory and CPU usage during the `build` phase, which is otherwise unconstrained. + +```shell +dokku resource:limit --memory 4g --process-type build node-js-app +``` + +``` +=====> Setting resource limits for node-js-app (build) + memory: 4g +``` + +Build-time limits are applied via the `docker-args-process-build` plugin trigger. They do not inherit from default (`_default_`) limits - only limits explicitly set against the `build` process type are applied at build time. This is intentional: build phases often require more memory than the runtime process, and silently inheriting a small runtime limit would cause confusing OOM failures. + +> [!WARNING] +> Do not use `build` as a runtime process type in your `Procfile`. The `build` name is reserved by the resource plugin to scope limits to the build container, and using it as a Procfile entry will cause those limits to be applied to that runtime container as well. + +Supported limits per builder: + +| Builder | `--cpu` | `--memory` | `--memory-swap` | `--nvidia-gpu` | +|------------|---------|------------|-----------------|----------------| +| herokuish | yes | yes | yes | yes | +| dockerfile | no | yes | yes | no | +| pack | no | no | no | no | +| nixpacks | no | no | no | no | +| railpack | no | no | no | no | +| lambda | no | no | no | no | + +Resource keys outside the supported set for a builder are silently ignored. Reservations (`resource:reserve`) are never applied at build time - only limits. + #### Clearing Resource Limits In cases where the values are incorrect - or there is no desire to limit resources - resource limits may be cleared using the `resource:limit-clear` command. diff --git a/docs/appendices/0.10.0-migration-guide.md b/docs/appendices/0.10.0-migration-guide.md index 95940eeb9ba..ec3aa37336a 100644 --- a/docs/appendices/0.10.0-migration-guide.md +++ b/docs/appendices/0.10.0-migration-guide.md @@ -4,10 +4,10 @@ By default, Dokku will ship a PCI Compliant nginx configuration. For developers whose users are on older browsers or mobile devices, you may need to ship a custom `nginx.conf.sigil` to enable ciphers for older browsers. -See the [nginx customization](/docs/configuration/nginx/#customizing-the-nginx-configuration) docs for more details. +See the [nginx customization](/docs/networking/proxies/nginx.md#customizing-the-nginx-configuration) docs for more details. ## Nginx Error Pages We now ship with nicer error pages by default. You are free to customize your Dokku installation via a custom `nginx.conf.sigil` to change what error pages are displayed in different circumstances. -See the [nginx customization](/docs/configuration/nginx/#customizing-the-nginx-configuration) docs for more details. +See the [nginx customization](/docs/networking/proxies/nginx.md#customizing-the-nginx-configuration) docs for more details. diff --git a/docs/appendices/0.21.0-migration-guide.md b/docs/appendices/0.21.0-migration-guide.md index 2f16cdb6ec0..a2131da6a44 100644 --- a/docs/appendices/0.21.0-migration-guide.md +++ b/docs/appendices/0.21.0-migration-guide.md @@ -8,7 +8,7 @@ The `tls` name is no longer a reserved app name, and can be used by applications - `git#git_deploy_branch()` is deprecated in favor of `plugn trigger git-deploy-branch`. - The `config` command is deprecated in favor of `config:show`. - - Usage of this command in conjunction with either the `--export` or `--shell` flag is deprecated in favor of `config:export --format` with the correct format value (`exports` or `shell`, respectively). + - Usage of this command in conjunction with either the `--export` or `--shell` flag is deprecated in favor of `config:export --format` with the correct format value (`exports` or `shell`, respectively). - The `nginx:build-config` command is deprecated in favor of `proxy:build-config`. ## Removals diff --git a/docs/appendices/0.23.0-migration-guide.md b/docs/appendices/0.23.0-migration-guide.md index b46cb8bc434..2853eafde9d 100644 --- a/docs/appendices/0.23.0-migration-guide.md +++ b/docs/appendices/0.23.0-migration-guide.md @@ -4,5 +4,5 @@ - The `plugin:list` command no longer outputs the version for the `plugn` binary. - Users building docker images that run Dokku will need to use a new sudoer wrapper for the `docker-image-labeler` binary to work correctly. A reference version has been placed in the `docker` skeleton directory. This should only impact platform developers, and users of our Docker image will already have the file available. -- The `dokku` user's cron is now in use by Dokku itself. Customizations will be overwritten. Users are encouraged to use a cron entry in `/etc/cron.d/dokku` to avoid this issue. +- The `dokku` user's cron is now in use by Dokku itself. Customizations will be overwritten. Users are encouraged to use a cron file at `/etc/cron.d/dokku` to avoid this issue. - As of 0.23.0, Dokku will now inject the `max-size` log driver option for applications. This is restricted to app-configured log driver values empty, `local` or `json-file` in 0.23.1 to increase setup compatibility. Users who configure alternative log drivers at the system level will need to either set the global `max-size` property to `unlimited` or switch to the built-in `vector` logging support. diff --git a/docs/appendices/0.25.0-migration-guide.md b/docs/appendices/0.25.0-migration-guide.md index 0730217dbfd..c6ebb660988 100644 --- a/docs/appendices/0.25.0-migration-guide.md +++ b/docs/appendices/0.25.0-migration-guide.md @@ -7,7 +7,9 @@ The [dokku-registry](https://github.com/dokku/dokku-registry) plugin is now buil - Builder plugins should call `post-release-builder` at the end of the build. - The `push` and `pull` command are not implemented. Users wishing to deploy a remote image should use `git:from-image`. Image pushing is not available at this time. - At this time, remote docker repositories are not automatically created for AWS, and users must create those repositories for their applications as necessary. This may be implemented in the future. -- Docker images are only pushed when configured to do so. See the [registry management documentation](/docs/deployment/registry-management.md) for more details. +- Docker images are only pushed when configured to do so. See the [registry management documentation](/docs/advanced-usage/registry-management.md) for more details. + +Before upgrading, uninstall the registry plugin via `dokku plugin:uninstall registry`. Not doing so will cause issues with Dokku. ## Other @@ -17,7 +19,7 @@ The [dokku-registry](https://github.com/dokku/dokku-registry) plugin is now buil - The `dokku run` command now always removes the ephemeral container on exit. Users that need a persistent container should instead specify a `console` process type in their `Procfile` specifying an available shell (usually either `bash` or `sh`) and scale that container appropriately. - The `pre-deploy` plugin trigger is now called internally by Dokku. Scheduler plugins should avoid calling this trigger, as any image changes introduced by subsequent trigger calls will be ignored. - The default image used for Herokuish Buildpack builds is now `gliderlabs/herokuish:latest-20`, and is based on both the `heroku-20` stack as well as Ubuntu 20.04. Users that wish to stick with the old, `heroku-18`/Ubuntu 18.04 builder may specify `gliderlabs/herokuish:latest-18` as their builder image. Please see the [herokuish buildpack deployment documentation](/docs/deployment/builders/herokuish-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder. - - This change does not impact any users of Cloud Native Buildpacks. + - This change does not impact any users of Cloud Native Buildpacks. - We fixed an issue that required extracting Procfiles at the beginning of a deploy. Due to this change, a `Dockerfile` app contains a `Procfile` that does not specify a `web` process will fail to start. Usage of a `Procfile` with _only_ a `release` command should be replaced with a `scripts.dokku.postdeploy` deployment task in the `app.json` file. The alternative is to define the `web` process in the `Procfile`. See the [deployment task documentation](/docs/advanced-usage/deployment-tasks.md) for more information. ### Deprecations diff --git a/docs/appendices/0.26.0-migration-guide.md b/docs/appendices/0.26.0-migration-guide.md index b752e8cc2c4..dbb3af106cd 100644 --- a/docs/appendices/0.26.0-migration-guide.md +++ b/docs/appendices/0.26.0-migration-guide.md @@ -9,4 +9,4 @@ - The `scheduler` plugin now controls the scheduler in use for deploys. Apps will have their `DOKKU_SCHEDULER` environment variables migrated to the scheduler plugin, after which that value will be removed from said app. Please see the [scheduler documentation](/docs/deployment/schedulers/scheduler-management.md) for more information. - The `deploy-source` metadata from `apps:report` is now no longer computed on the fly, but hydrated at deploy time via the `deploy-source-set` trigger. This value may be empty until your next deploy. - - Additionally, the `deploy-source` trigger has now been removed. + - Additionally, the `deploy-source` trigger has now been removed. diff --git a/docs/appendices/0.27.0-migration-guide.md b/docs/appendices/0.27.0-migration-guide.md new file mode 100644 index 00000000000..1519f4b5b91 --- /dev/null +++ b/docs/appendices/0.27.0-migration-guide.md @@ -0,0 +1,14 @@ +# 0.27.0 Migration Guide + +## Changes + +- Renaming an application will now only rename domains that are associated with global domains. As an example: + + ```shell + dokku domains:set-global dokku.me + dokku apps:create node-js-app + dokku domains:set node-js-app node-js-app.dokku.me node-js-app.dokku.com + dokku apps:rename node-js-app other-name + dokku domains:report node-js-app --domains-app-vhosts + # output: other-name.dokku.me node-js-app.dokku.com + ``` diff --git a/docs/appendices/0.28.0-migration-guide.md b/docs/appendices/0.28.0-migration-guide.md new file mode 100644 index 00000000000..e308cf388ae --- /dev/null +++ b/docs/appendices/0.28.0-migration-guide.md @@ -0,0 +1,25 @@ +# 0.28.0 Migration Guide + +## Removals + +Support for the following operating systems has been removed: + +- CentOS 7 +- Debian 9 (Stretch) +- Fedora +- OpenSuse + +Dokku will no longer provide packages for RPM based systems. Debian 9 users _may_ use new Debian packages, though support is not guaranteed. + +All users are encouraged to install Dokku via the [Docker-based installation method](/docs/getting-started/install/docker.md), or switch to a supported operating system. + +## Deprecations + +Ubuntu 18.04 is now a deprecated installation target. The operating system will be considered EOL by Canonical in April 2023. Users are encouraged to upgrade to Ubuntu 22.04 or consider switching their instllation method to the [Docker-based installation method](/docs/getting-started/install/docker.md) to avoid any disruption in usage. + +## Additions + +New in 0.28.0 are the Caddy, Haproxy and Traefik plugins. As community plugins wrapping these proxies exist, users should: + +- Recommended: Uninstall the community plugin in question and switch all config to the new plugins. +- Upgrade the community plugin to a version that does not use the `proxy:set` value of `caddy`, `haproxy` or `traefik`. diff --git a/docs/appendices/0.29.0-migration-guide.md b/docs/appendices/0.29.0-migration-guide.md new file mode 100644 index 00000000000..bd1e08ea654 --- /dev/null +++ b/docs/appendices/0.29.0-migration-guide.md @@ -0,0 +1,19 @@ +# 0.29.0 Migration Guide + +## Changes + +- The output of `run:detached` now uses the container name - eg. `node-js-app.run.1` - vs the container id. +- The ID of `cron` tasks is now base36-encoded instead of base64-encoded. +- The `nginx.conf.sigil` is now extracted when source code is extracted for a build and not from the built image. Users can specify alternative paths via the `nginx-conf-sigil-path` property of the `nginx` plugin. See the [nginx documentation](/docs/networking/proxies/nginx.md#customizing-the-nginx-configuration) for more information on how to configure the `nginx.conf.sigil` path for your application. + - For deploys via `git:from-image`, the `nginx.conf.sigil` file will be extracted from the source image, respecting the value of `nginx-conf-sigil-path`. +- The `Procfile` is now extracted when source code is extracted for a build and not from the built image. Users can specify alternative paths via the `procfile-path` property of the `ps` plugin. See the [process management documentation](/docs/processes/process-management.md#changing-the-procfile-location) for more information on how to configure the `Procfile` path for your application. + - For deploys via `git:from-image`, the `Procfile` file will be extracted from the source image, respecting the value of `procfile-path`. +- The existing `pre-restore` hook has been renamed to `scheduler-pre-restore`. There is a new `pre-restore` hook that is triggered within the `ps:restore` command prior to restoring any apps. +- Nginx init commands are now performed via systemctl on Ubuntu systems when `/usr/bin/systemctl` is available. + +## Removals + +- The `DOKKU_WAIT_TO_RETIRE` environment variable has been migrated to a `checks` property named `wait-to-retire` and will be ignored if set as an environment variable. +- The `domains-setup` trigger has been removed. Initial app domains will now be automatically setup during app creation. +- The `URLS` file containing generated urls for an app is no longer generated or referenced. Users should retrieve app urls via the new `domains-urls` plugin trigger. +- The common function `get_app_urls` has been removed. Users should retrieve app urls via the new `domains-urls` plugin trigger. diff --git a/docs/appendices/0.30.0-migration-guide.md b/docs/appendices/0.30.0-migration-guide.md new file mode 100644 index 00000000000..531a417ea21 --- /dev/null +++ b/docs/appendices/0.30.0-migration-guide.md @@ -0,0 +1,41 @@ +# 0.30.0 Migration Guide + +> **Important**: Due to the removal of `DOKKU_SCALE` support, users with a version older than 0.25.x are heavily encouraged to upgrade to 0.29.x prior to 0.30.x. Not doing so will result in all app containers stopping on rebuild due to having no scale settings. + +## Deprecations + +- Support for Ubuntu 18.04 has been deprecated. Please upgrade your host OS in advance of the [End Of Life in April 2023](https://wiki.ubuntu.com/BionicBeaver/ReleaseNotes). + +## Changes + +- The `app.json` file is now extracted from the source code instead of the built image. For deploys via `git:from-image`, the file is extracted from the built image. + +## Removals + +- Support for [SPDY](https://en.wikipedia.org/wiki/SPDY) has been removed. No major browser supports it as of 2021. Custom `nginx.conf.sigil` templates referencing spdy-related variables will continue to build until the 1.0.0 release. +- Support for the `DOKKU_SCALE` file - deprecated in 0.25.0 - has been removed in favor of the `formation` key in the `app.json` file. Please see the [process management documentation](/docs/processes/process-management.md#manually-managing-process-scaling) for more information on how to use the `formation` key of the `app.json` file. +- The deprecated `--detach` global flag for `dokku run` was removed. Please see the [one-off tasks documentation](/docs/processes/one-off-tasks.md#running-a-detached-container) for more information on running detached containers. +- The following deprecated trigger have been removed in favor of the `post-release-builder` trigger. See the [plugin triggers documentation](https://dokku.com/docs/development/plugin-triggers/#post-release-builder) for more details. + - `post-release-buildpack` + - `post-release-dockerfile` + - `post-release-pack` +- The ability to call `logs:failed` without specifying an app or `--all` flag has been removed. This was deprecated in 0.22.0. Please see the [logs:failed](/docs/deployment/logs.md#failed-deploy-logs). +- The following app shell functions - deprecated since 0.20.0 - have been removed in favor of their plugin trigger equivalents. Sourcing the `app/functions` file will fail going forward. + - `apps#apps_create()` is removed in favor of `plugn trigger app-create`. + - `apps#apps_destroy()` is removed in favor of `plugn trigger app-destroy`. + - `apps#apps_exists()` is removed in favor of `plugn trigger app-exists`. + - `apps#apps_maybe_create()` is removed in favor of `plugn trigger app-maybe-create`. +- The following common shell functions have been removed: + - `common#is_container_running()` (deprecated since 0.12.6) is removed in favor of `common#is_container_status()`. + - `common#is_app_running()` (deprecated since 0.22.0) is removed in favor of `ps#fn-ps-is-app-running()`. +- The global `--rm-container` and `--rm` flags - deprecated since 0.25.0 - have been removed. +- The following git shell functions have been removed: + - `git#use_git_worktree()` (deprecated since 0.23.7) has been removed. There is no alternative as the function has been made internal. + - `git#git_deploy_branch()`(deprecated since 0.21.0) has been removed in favor of `plugn trigger git-deploy-branch`. +- The following nginx commands - deprecated since 0.20.0 - have been removed: + - `nginx:show-conf` has been removed in favor of `nginx:show-config`. + - `nginx:validate` has been removed in favor of `nginx:validate-config`. + - `nginx:build-config` - deprecated since 0.21.0 - has been removed in favor of `proxy:build-config`. +- The following proxy functions - deprecated since 0.20.0 - have been removed in favor of their plugin trigger equivalents. Sourcing the `proxy/functions` file will fail going forward. + - `proxy#is_app_proxy_enabled()` is removed in favor of `plugn trigger proxy-is-enabled`. + - `proxy#get_app_proxy_type()` is removed in favor of `plugn trigger proxy-type`. diff --git a/docs/appendices/0.31.0-migration-guide.md b/docs/appendices/0.31.0-migration-guide.md new file mode 100644 index 00000000000..a5636568b2e --- /dev/null +++ b/docs/appendices/0.31.0-migration-guide.md @@ -0,0 +1,36 @@ +# 0.31.0 Migration Guide + +## Changes + +- Herokuish build cache is now mounted from a docker volume - eg. `cache-node-js-app` - instead of the local filesystem. All existing app cache will be cleared upon upgrading past 0.29.0. +- The `vector` container integration now mounts config to `/etc/vector` instead of the path `/etc/vector/vector.json`, allowing users the ability to provide extra configuration for Vector Sinks. To take advantage of the new functionality, the vector container should be stopped (via `dokku logs:vector-stop`) and then started (via `dokku logs:vector-start`). +- The `traefik` integration now mounts config to `/data` instead of the path `/acme.json`, fixing permissions issues under certain architectures. To take advantage of the new functionality, the traefik container should be stopped (via `dokku traefik:stop`) and then started (via `dokku traefik:start`). +- Users no longer need to clear the `source-image` git property when transitioning from image-based deploys (`git:from-image` and `git:load-image`) to other deployment methods (git push, `git:from-archive`, `git:sync`). +- For deploys via the `git:from-image` and `git:load-image` commands, the `CHECKS` file is now extracted from the configured `WORKDIR` property of the image. For all other deploys - git push, `git:from-archive`, `git:sync` - will have the `CHECKS` extracted directly from the source code. The filename in both cases is `CHECKS` and cannot be modified. +- Port mappings are now auto-detected during the build process. Users may override detected port mappings via the `ports` plugin. A default port mapping of `http:80:5000` will be used if none is otherwise specified. +- Users building docker images that run Dokku will need to use a new sudoer wrapper for the `docker-container-healthchecker` binary to work correctly. A reference version has been placed in the `docker` skeleton directory. This should only impact platform developers, and users of our Docker image will already have the file available. +- The default image used for Herokuish Buildpack builds is now `gliderlabs/herokuish:latest-22`, and is based on both the `heroku-22` stack as well as Ubuntu 22.04. Users that wish to stick with the old, `heroku-20`/Ubuntu 20.04 builder may specify `gliderlabs/herokuish:latest-20` as their builder image. Please see the [herokuish buildpack deployment documentation](/docs/deployment/builders/herokuish-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder. +- The default image used for Cloud Native Buildpack builds is now `heroku/builder:22`, and is based on both the `heroku-22` stack as well as Ubuntu 22.04. Users that wish to stick with the old, `heroku-20`/Ubuntu 20.04 builder may specify `heroku/buildpacks:20` as their builder image. Please see the [herokuish buildpack deployment documentation](/docs/deployment/builders/herokuish-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder. + +## Deprecations + +- The `proxy:ports*` commands have been replaced with the new `ports` plugin. Users will be able to use the old `proxy:ports*` commands for a single minor release, and they will be removed in the next minor release. +- The `common#get_available_port()` function has been deprecated and will be removed in the next release. Users should avoid interacting with this function and instead use the `ports-get-available` plugin trigger for fetching an available port. +- The `proxy-configure-ports` plugin trigger has been deprecated and will be removed in the next release. Users should instead trigger the `ports-configure` plugin trigger. +- The `common#get_dockerfile_exposed_ports()` function is deprecated and will be removed in the next release. There is no replacement for this as it's only use in Dokku core was in the `builder-dockerfile` plugin. +- The `common#get_exposed_ports_from_image()` function is deprecated and will be removed in the next release. There is no replacement for this as it's only use in Dokku core was during the build process. +- The environment variable `DOKKU_PROXY_PORT_MAP` has been migrated to the properties system. Direct changes to the value will be ignored, and users should interact with port maps via the `ports` plugin. +- The `CHECKS` file is deprecated in favor of defining healthchecks in the `app.json` file. The [docker-container-healthchecker](https://github.com/dokku/docker-container-healthchecker) tool can be used to generate healthcheck entries in `app.json` format from existing `CHECKS` files. See the [zero-downtime deploy documentation](/docs/deployment/zero-downtime-deploys.md) for more information on how the new zero downtime check format works. +- ARM support is now deprecated, and will be removed in the next minor release. ARM releases are not tested in CI and do not get testing locally. Users of ARM installations on platforms such as the Raspberry PI should consider migrating to ARM64. ARM64 will continue to be supported by Dokku, and there are no plans to remove support for ARM64. + +## Un-Deprecations + +- The bare `apps` and `config` cli aliases are no longer deprecated. This better follows Heroku's output and is more useful to users. The subcommands will be treated as the primary, documented versions, while the aliases are there for convenience. + +## Removals + +- The variable `RAW_TCP_PORTS` is no longer exposed for `nginx.conf.sigil` templating. +- The environment variable `DOKKU_DOCKERFILE_PORTS` is no longer in use. Users wishing to change port mappings should instead use the `ports` plugin. +- The `common#get_app_raw_tcp_ports()` function has been removed in the next release. Users should instead use the `ports-get` plugin trigger for fetching ports for an app. +- The plugin trigger `network-compute-ports` has been removed. It's only use was in the `scheduler-docker-local` plugin, for exposing ports. It's functionality is now implemented via the `ports` plugin. +- The plugin trigger `network-get-port` has been removed. It's only use was in the `scheduler-docker-local` plugin, for recording ports. It's functionality is now implemented via the `ports` plugin. diff --git a/docs/appendices/0.32.0-migration-guide.md b/docs/appendices/0.32.0-migration-guide.md new file mode 100644 index 00000000000..9775e01d14d --- /dev/null +++ b/docs/appendices/0.32.0-migration-guide.md @@ -0,0 +1,30 @@ +# 0.32.0 Migration Guide + +## Deprecations + +- The following `pre-build-*` plugin triggers have been deprecated and will be removed in the next release. Users should instead trigger the `pre-build` plugin trigger. + - pre-build-buildpack + - pre-build-dockerfile + - pre-build-lambda + - pre-build-pack +- The following `pre-release-*` plugin triggers have been deprecated and will be removed in the next release. Users should instead trigger the `pre-release-builder` plugin trigger. + - pre-release-buildpack + - pre-release-dockerfile + - pre-release-lambda + - pre-release-pack +- The following `post-build-*` plugin triggers have been deprecated and will be removed in the next release. Users should instead trigger the `post-build` plugin trigger. + - post-build-buildpack + - post-build-dockerfile + - post-build-lambda + - post-build-pack +- The `CHECKS` file was deprecated in the previous release in favor of defining healthchecks in the `app.json` file. It's support and auto-migration to healthchecks in the `app.json` file will be removed in the next release. See the [zero-downtime deploy documentation](/docs/deployment/zero-downtime-deploys.md) for more information on how the new zero downtime check format works. + +## Removals + +- ARM support has been removed. Users of ARM installations on platforms such as the Raspberry PI should consider migrating to ARM64. ARM64 will continue to be supported by Dokku, and there are no plans to remove support for ARM64. +- The following proxy-port related code has been removed. It is replaced by the `ports` plugin and it's related plugin triggers. + - `proxy:ports-*` commands + - `proxy-configure-ports` plugin trigger + - `common#get_available_port()` + - `common#get_dockerfile_exposed_ports()` + - `common#get_exposed_ports_from_image()` diff --git a/docs/appendices/0.33.0-migration-guide.md b/docs/appendices/0.33.0-migration-guide.md new file mode 100644 index 00000000000..cd00662bdc1 --- /dev/null +++ b/docs/appendices/0.33.0-migration-guide.md @@ -0,0 +1,3 @@ +# 0.33.0 Migration Guide + +Dokku 0.33.0 did not introduce any refactors, removals, or deprecations, and focused solely on system improvements. diff --git a/docs/appendices/0.34.0-migration-guide.md b/docs/appendices/0.34.0-migration-guide.md new file mode 100644 index 00000000000..d6d1578431f --- /dev/null +++ b/docs/appendices/0.34.0-migration-guide.md @@ -0,0 +1,23 @@ +# 0.34.0 Migration Guide + +## Removals + +- The `disable-chown` property of the `scheduler-docker-local` plugin has been removed. Mounted paths will no longer have file permissions changed during the pre-deploy phase. Files baked into the container image for herokuish builds will always be owned by the correct user. +- The `git:unlock` command has been removed. It was previously used to "unlock" a temporary directory that already existed. The directory the `git:unlock` command used to cleanup is now properly removed on exit of the `git:from-image` command. +- The k3s plugin no longer supports port mappings other than `https:443` and `http:80`. If no matching port map is specified, Dokku will use expose the app on the first port mapping, with priority given to `https` mappings over `http`. +- Golang functions to invoke plugin triggers named `PlugnTrigger*` have been removed in favor of `CallPlugnTrigger`. + +## Changes + +- The k3s scheduler now creates an Ingress object per domain instead of one per port mapping. This will cause a short amount of downtime during the next app deploy after upgrading to 0.34.0 +- App lock files have been removed from the `/home/dokku` directory and moved into the `data/apps` directory. +- Logs are now rotated via `copytruncate` instead of `create` in logrotate. +- The k3s scheduler now defaults to nginx as it's default proxy implementation. The traefik proxy implementation is still available, though users will need to set the global `ingress-class` k3s property to `traefik` via the following command: + + ```shell + dokku scheduler-k3s:set --global ingress-class traefik + ``` + +## Deprecations + +The `pre-deploy` plugin trigger is deprecated as of `0.34.4`. It is currently invoked during the `post-release-builder` plugin trigger, where image mutation is heavily discouraged. Users should instead move any trigger usage to the `pre-release-builder` plugin trigger. The `pre-deploy` plugin trigger will be removed in a future release. diff --git a/docs/appendices/0.35.0-migration-guide.md b/docs/appendices/0.35.0-migration-guide.md new file mode 100644 index 00000000000..e12bbc38325 --- /dev/null +++ b/docs/appendices/0.35.0-migration-guide.md @@ -0,0 +1,17 @@ +# 0.35.0 Migration Guide + +## Changes + +- The provided `dokku/dokku:latest` Docker image now runs Ubuntu 24.04 Noble. Users that install custom packages within their docker-based installations should be sure to check package compatibility with Ubuntu 24.04. +- The default image used for Cloud Native Buildpack builds is now `heroku/builder:24`, and is based on both the `heroku-24` stack as well as Ubuntu 24.04. Users that wish to stick with the old, `heroku-22`/Ubuntu 22.04 builder may specify `heroku/buildpacks:22` as their builder image. Please see the [cloud native buildpack deployment documentation](/docs/deployment/builders/cloud-native-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder. +- The default image used for Herokuish Buildpack builds is now `gliderlabs/herokuish:latest-24`, and is based on both the `heroku-24` stack as well as Ubuntu 24.04. Users that wish to stick with the old, `heroku-22`/Ubuntu 22.04 builder may specify `gliderlabs/herokuish:latest-22` as their builder image. Please see the [herokuish buildpack deployment documentation](/docs/deployment/builders/herokuish-buildpacks.md#customizing-the-buildpack-stack-builder) for more information on how to specify a custom buildpack stack builder. +- The `vector` image used by Dokku for enhanced logging support has been upgraded from 0.39.x to 0.41.x. To take advantage of the new image, execute the following commands: + ``` + dokku logs:vector-stop + dokku logs:vector-start + ``` +- The `openresty` image used by Dokku for proxying via openresty has been upgraded from 0.8.0 to 0.9.0. To take advantage of the new image, execute the following commands: + ``` + dokku openresty:stop + dokku openresty:start + ``` diff --git a/docs/appendices/0.36.0-migration-guide.md b/docs/appendices/0.36.0-migration-guide.md new file mode 100644 index 00000000000..2caeca2885c --- /dev/null +++ b/docs/appendices/0.36.0-migration-guide.md @@ -0,0 +1,12 @@ +# 0.36.0 Migration Guide + +## Changes + +- Process stop timeouts are now configured via the `ps` property `stop-timeout-seconds`. Existing `DOKKU_DOCKER_STOP_TIMEOUT` environment variables will be automatically migrated to the new value. +- Processes now default to a `30` second stop timeout. +- The `domains:clear` command no longer runs domain setup after clearing the domains. Use `domains:reset` or run `domains:setup` manually to have the same effect. +- The deprecated `DOKKU_DISABLE_ANSI_PREFIX_REMOVAL` setting has been removed. Dokku deploys no longer use shell escape codes to modify output, and thus deploy output may now contain `remote:` as a prefix in each lines output. + +## Deprecations + +Ubuntu 20.04 is now a deprecated installation target. The operating system will be considered EOL by Canonical in April 2025. Users are encouraged to upgrade to Ubuntu 24.04 or consider switching their instllation method to the [Docker-based installation method](/docs/getting-started/install/docker.md) to avoid any disruption in usage. diff --git a/docs/appendices/0.37.0-migration-guide.md b/docs/appendices/0.37.0-migration-guide.md new file mode 100644 index 00000000000..b06b9c81d1e --- /dev/null +++ b/docs/appendices/0.37.0-migration-guide.md @@ -0,0 +1,15 @@ +# 0.37.0 Migration Guide + +## Changes + +- The `app.json` file format is now parsed as JSONC and supports inline comments. +- The `scheduler-k3s:cluster-add` command is now `scheduler-k3s:cluster:add`. +- The `scheduler-k3s:cluster-list` command is now `scheduler-k3s:cluster:list`. +- The `scheduler-k3s:cluster-remove` command is now `scheduler-k3s:cluster:remove`. + +## Removals + +- Support for the `DOKKU_DOCKERFILE_CACHE_BUILD` environment variable has been removed. Use the `docker-options` plugin to set build arguments for your app instead. +- Support for the `DOKKU_DOCKER_BUILD_OPTS` environment variable has been removed. Use the `docker-options` plugin to set build arguments for your app instead. +- Support for the `no-cache` nixpacks property has been removed. Use the `docker-options` plugin to set build arguments for your app instead. +- Support for customizing the label-key for the caddy proxy has been removed. Always use `caddy.` as a prefix for your caddy directives. diff --git a/docs/appendices/0.38.0-migration-guide.md b/docs/appendices/0.38.0-migration-guide.md new file mode 100644 index 00000000000..254446dc43d --- /dev/null +++ b/docs/appendices/0.38.0-migration-guide.md @@ -0,0 +1,69 @@ +# 0.38.0 Migration Guide + +## Changes + +- Dokku now generates a minimal nginx configuration for apps without running `web` processes (undeployed apps, apps with no `web` process type, or apps with stopped web processes). This configuration returns `502 Bad Gateway` responses, ensuring the app's domain resolves and monitoring tools can detect non-200 status codes. The configuration is automatically replaced with the full proxy configuration once the app is deployed with running `web` processes. See the [nginx documentation](/docs/networking/proxies/nginx.md#nginx-configuration-for-undeployed-apps) for more details. +- Users with custom `nginx.conf.sigil` templates that reference `DOKKU_APP_WEB_LISTENERS` should be aware that this variable may now be empty when the template is rendered for apps without running web processes. Custom templates should handle this case gracefully, for example by using a conditional to serve an error page instead of proxying: + + ``` + location / { + {{ if $.DOKKU_APP_WEB_LISTENERS }} + proxy_pass http://{{ $.APP }}-{{ $upstream_port }}; + {{ else }} + return 502; + {{ end }} + } + ``` + +- The path on disk to both the global `ENV` file and app `ENV` files have been moved. Users should reference environment variables via the provided plugin triggers rather than directly sourcing the ENV files. Existing ENV files are left untouched and will be removed on the subsequent Dokku install. +- During a fresh apt install, the upstream nginx default vhost files (`/etc/nginx/sites-enabled/default`, `/etc/nginx/sites-available/default`, and `/etc/nginx/conf.d/default.conf`) are renamed to `${path}.dokku-disabled` (not deleted) to avoid a `duplicate default server for 0.0.0.0:80` error. Operators with local customizations can recover them by inspecting the `.dokku-disabled` siblings. Upgrade-in-place installs do not touch any existing nginx files. +- Fresh apt installs now ship a catch-all default site at `/etc/nginx/conf.d/00-default-vhost.conf` that rejects requests with unknown Host headers using `ssl_reject_handshake on` (HTTPS) and `return 444` (HTTP). This replaces the manual workaround previously documented in the nginx docs. The behavior can be opted out at install time via the `dokku/install_default_site` debconf prompt. On nginx older than 1.19.4 (e.g., Debian Bullseye's nginx 1.18.0), the postinst installs an HTTP-only variant of the catch-all that omits the SSL listener and `ssl_reject_handshake`, since that directive is unsupported on those versions. See the [Default site documentation](/docs/networking/proxies/nginx.md#default-site). +- The `docker-local` scheduler now sends `SIGTERM` to old containers immediately after a successful deploy, rather than waiting `wait-to-retire` seconds before signaling. This matches Heroku's graceful-shutdown contract and lets applications begin draining in-flight work as soon as proxy traffic switches. The `wait-to-retire` grace period and `stop-timeout-seconds` hard-stop continue to apply as before. See the [zero downtime deploys documentation](/docs/deployment/zero-downtime-deploys.md#wait-to-retire) for more details. +- The `docker-local` scheduler no longer queues an image for retirement when another running container of the same app still uses it. This fixes the case where a `ps:rebuild` against an image-based deploy (`git:from-image`) produced an identical-SHA image and the `dokku-retire` cron timer would log `Image ... has running containers, skipping rm` on every run. Stuck entries from prior versions are pruned automatically on the next `ps:retire` run. +- All `:report` subcommands now accept the `--global` flag, which scopes the report to globally-configured properties. The flag composes with `--format json`, so a JSON report of global properties can be obtained via, for example, `dokku scheduler:report --global --format json`. Previously, combining `--global` with `--format json` was rejected with an "info flag" error, and `--global` on its own was treated as an unknown flag. +- Every `:report` key of the form `-global-` now returns the raw stored global value (empty when the property has never been set) instead of the resolved value with the built-in default substituted in. A new `-computed-` key has been added wherever a default existed and returns the effective value (per-app value, falling back to the global value, then to the built-in default). This affects `app-json`, `builder-dockerfile`, `builder-herokuish`, `builder-lambda`, `builder-nixpacks`, `builder-pack`, `builder-railpack`, `caddy`, `checks`, `git`, `haproxy`, `logs`, `network`, `nginx`, `openresty`, `proxy`, `registry`, `scheduler`, `scheduler-k3s` and `traefik`. External tooling that read `-global-` and depended on the default value should switch to `-computed-`. The bare `-` keys for caddy/haproxy/traefik/openresty global-only properties (`caddy-image`, `haproxy-log-level`, `traefik-api-enabled`, `openresty-image`, `openresty-letsencrypt-email`, `openresty-letsencrypt-server`, `openresty-allowed-letsencrypt-domains-func-base64`, etc.) have also been replaced by the `global-` and `computed-` pair. +- The `ps` and `cron` plugins now follow the same `-global-` / `-computed-` convention. The `ps:report` keys `stop-timeout-seconds`, `global-stop-timeout-seconds`, and `computed-stop-timeout-seconds` have been renamed to `ps-stop-timeout-seconds`, `ps-global-stop-timeout-seconds`, and `ps-computed-stop-timeout-seconds`. The `cron:report` keys `cron-mailfrom` and `cron-mailto` have been renamed to `cron-global-mailfrom` and `cron-global-mailto`, and new `cron-computed-mailfrom` and `cron-computed-mailto` keys have been added. The corresponding user-facing `--` arguments to `ps:report` and `cron:report` were renamed alongside the JSON keys; no aliases are kept. Additionally, both `ps:report --global` and `cron:report --global` now emit the `-computed-` keys for every settable global property with a default (`ps-computed-procfile-path`, `ps-computed-stop-timeout-seconds`, `cron-computed-maintenance`, `cron-computed-mailfrom`, `cron-computed-mailto`), matching the shape used by the other plugins. +- A second round of `:report` additions surfaces every remaining settable-but-unreported property under the same raw/global/computed convention so external tooling can verify drift via `:report --format json` without falling back to a generic bash task. The `ps` plugin gains `--ps-dockerfile-start-cmd` and `--ps-computed-dockerfile-start-cmd`, `--ps-start-cmd` and `--ps-computed-start-cmd`, and the `--ps-skip-deploy` / `--ps-global-skip-deploy` / `--ps-computed-skip-deploy` triple (default `false`). The `builder` plugin gains the `--builder-skip-cleanup` triple (default `false`). The `scheduler` plugin gains the `--scheduler-shell` triple. The `proxy` plugin gains the `--proxy-proxy-port` and `--proxy-proxy-ssl-port` triples and exposes the raw `disabled` property as `--proxy-disabled` / `--proxy-computed-disabled`, alongside the existing inverted `--proxy-enabled`. The `openresty` plugin gains `--openresty-global-log-level` and `--openresty-computed-log-level` (default `ERROR`). The `nginx` plugin gains the `--nginx-nginx-service-command` triple. The `scheduler-k3s` plugin gains `--scheduler-k3s-global-token`, but the value is masked as `*******` in default stdout output; the raw value is returned only when the report is requested via `--format json` or when this flag is queried explicitly by name. The traefik `dns-provider-` keys now follow the same explicit-query rule - previously they were unmasked only for `--format json`, but a query like `dokku traefik:report --traefik-dns-provider-cf_api_key` now returns the actual value instead of `*******`. +- All Go-implemented plugins (`app-json`, `apps`, `builder`, `buildpacks`, `builds`, `cron`, `docker-options`, `logs`, `network`, `ports`, `proxy`, `ps`, `registry`, `resource`, `scheduler`, `scheduler-k3s`, `storage`) now emit JSON keys from `:report --format json` without the `-` head segment, matching the shape bash plugins have always emitted. For example, `dokku ps:report myapp --format json` now contains `stop-timeout-seconds`, `global-stop-timeout-seconds`, and `computed-stop-timeout-seconds` keys. The CLI flag names (`--ps-stop-timeout-seconds`, etc.) are unchanged, and `:set` semantics are unchanged. For backwards compatibility during the 0.38.x patch series, the old `-` JSON keys are emitted side-by-side with the new keys, so external scripts reading either shape continue to work. The legacy keys will be dropped in a future major release. External JSON consumers should migrate to the new key shape. +- The `scheduler-k3s` plugin now manages env config and the dokku-generated image pull Secret as their own helm releases with stable names (`config-{app}` and `pull-secret-{app}`) rather than bundling them into the app helm chart with a per-deploy timestamp suffix (`env-{app}.{ts}` / `ims-{app}.{ts}`). This fixes two bugs: a helm rollback of the app chart no longer deletes Secrets that older ReplicaSets still reference, and the Deployment's `imagePullSecrets` list no longer accumulates references to nonexistent Secrets across deploys. The next deploy of an app switches the Deployment's `envFrom` and `imagePullSecrets` references to the stable names and prunes any leaked entries; existing live Deployments do not need to be patched manually. App rename now also uninstalls the old `tls-{app}`, `config-{app}`, and `pull-secret-{app}` releases under the previous app name; the new name's releases are recreated on the next deploy or certs sync. +- **New in 0.38.25:** Values supplied through docker options, `dokku run`'s `-e`/`--env` flag, and `--ttl-seconds` are no longer evaluated by the shell when assembling a container's arguments; they are now tokenized and passed through verbatim. This closes a command-injection vector where a `$(...)` or backtick expression in one of these values executed on the host as the `dokku` user during build, deploy, or run. As a result, shell metacharacters such as `$(...)`, backticks, `$VAR`, and globs in these values are treated literally instead of being expanded, and `--ttl-seconds` must now be a plain integer. Existing Traefik docker-options labels (those whose label key begins with `traefik.`) whose backticks were stored with a stray backslash are repaired automatically the first time `dokku` runs after the upgrade, so they become valid on the next deploy. +- The storage plugin now treats persistent volumes as named, scheduler-aware first-class resources via `storage:create`, `storage:mount`, `storage:set`, and `storage:destroy`. The legacy `storage:mount :` colon form continues to work on docker-local apps but is deprecated; on k3s apps it is rejected. Existing colon-form mounts are migrated automatically the first time the new storage plugin runs (during the install trigger) - they appear as `legacy-` entries in `storage:list-entries`. The migration is idempotent and tied to a per-app flag file at `$DOKKU_LIB_ROOT/config/storage/.migrated/`; deleting that file forces a re-scan on the next install. The `storage:ensure-directory` command keeps working but now emits a deprecation warning - prefer `storage:create []` (the path defaults to the same `$DOKKU_LIB_ROOT/data/storage/` location). Storage entry names must now be DNS-1123 labels of 45 characters or less so they can be used verbatim as Helm release and Kubernetes resource names; underscores and uppercase characters that the older `ensure-directory` validator accepted are rejected for new names. The migration synthesizer always uses lowercase hex hashes so existing data is never locked out. + +### TLS handshake behavior change + +With the new catch-all installed on nginx 1.19.4+, an HTTPS request to a hostname that matches a configured dokku app but where the app has no TLS certificate configured will have its TLS handshake rejected by the catch-all (via `ssl_reject_handshake on`). Previously, nginx fell through to the lexicographically first port-443 server block and presented that block's certificate, producing a cert-mismatch error on the client. The new behavior is a correctness improvement, but operators who deliberately relied on the old fall-through certificate (for monitoring probes, for example) need to either configure a certificate for the target app or remove the catch-all on that host. Existing apps that already have certificates configured are unaffected: nginx selects the right server block via SNI before TLS completion, so the catch-all is never consulted for legitimate requests. + +This change does not apply to nginx older than 1.19.4 (e.g., Debian Bullseye's nginx 1.18.0), where the catch-all is installed as an HTTP-only variant. On those systems, HTTPS handshakes to unknown hosts continue to fall through to the first port-443 server block as before. + +### Environment variables migrated to plugin properties + +A number of `DOKKU_*` config environment variables have been replaced with properly-namespaced plugin properties. Existing values are migrated automatically the first time `dokku` runs after the upgrade, and the original config variable is unset. No manual action is required. + +Going forward, configure these settings using the property commands listed below. Setting the deprecated env vars via `dokku config:set` will no longer have any effect. + +| Deprecated Env Var | Replacement Command | +|---|---| +| `DOKKU_APP_PROXY_TYPE` | `dokku proxy:set type ` | +| `DOKKU_APP_RESTORE` | `dokku ps:set restore ` | +| `DOKKU_APP_SHELL` | `dokku scheduler:set shell ` | +| `DOKKU_CHECKS_DISABLED` | `dokku checks:disable [proctypes]` | +| `DOKKU_CHECKS_ENABLED` | `dokku checks:enable [proctypes]` | +| `DOKKU_CHECKS_SKIPPED` | `dokku checks:skip [proctypes]` | +| `DOKKU_CHECKS_WAIT` | `dokku checks:set wait ` | +| `DOKKU_CHECKS_TIMEOUT` | `dokku checks:set timeout ` | +| `DOKKU_CHECKS_ATTEMPTS` | `dokku checks:set attempts ` | +| `DOKKU_DEFAULT_CHECKS_WAIT` | `dokku checks:set --global default-wait ` | +| `DOKKU_DISABLE_APP_AUTOCREATION` | `dokku apps:set --global disable-autocreation ` | +| `DOKKU_DISABLE_PROXY` | `dokku proxy:disable ` / `dokku proxy:enable ` | +| `DOKKU_DOCKERFILE_START_CMD` | `dokku ps:set dockerfile-start-cmd ` | +| `DOKKU_PROXY_PORT` | `dokku proxy:set proxy-port ` | +| `DOKKU_PROXY_SSL_PORT` | `dokku proxy:set proxy-ssl-port ` | +| `DOKKU_SKIP_ALL_CHECKS` | `dokku checks:disable ` | +| `DOKKU_SKIP_CLEANUP` | `dokku builder:set skip-cleanup ` | +| `DOKKU_SKIP_DEFAULT_CHECKS` | `dokku checks:skip ` | +| `DOKKU_SKIP_DEPLOY` | `dokku ps:set skip-deploy ` | +| `DOKKU_START_CMD` | `dokku ps:set start-cmd ` | + +`DOKKU_PARALLEL_ARGUMENTS` is removed entirely; it has no replacement. + +`DOKKU_SKIP_CLEANUP` continues to be honored when set in `/etc/environment` or `~dokku/.dokkurc/*` so that bootstrap-time configuration keeps working, but the `builder skip-cleanup` property is the canonical interface and takes precedence when set. diff --git a/docs/appendices/0.5.0-migration-guide.md b/docs/appendices/0.5.0-migration-guide.md index 2daba27dd7b..fe0f1fd6bc3 100644 --- a/docs/appendices/0.5.0-migration-guide.md +++ b/docs/appendices/0.5.0-migration-guide.md @@ -3,23 +3,23 @@ ## `nginx-vhosts` plugin - The nginx-vhosts template language is now [sigil](https://github.com/gliderlabs/sigil) - - No need to escape literal `$` characters (or other "bash-isms") - - Template variables are represented as {{ .VARIABLE_NAME }} - - A detailed list of template variables can be found [here](/docs/configuration/nginx.md#available-template-variables) + - No need to escape literal `$` characters (or other "bash-isms") + - Template variables are represented as {{ .VARIABLE_NAME }} + - A detailed list of template variables can be found [here](/docs/networking/proxies/nginx.md#available-template-variables) - A custom nginx-vhosts template must be named `nginx.conf.sigil` - - The default path for this custom template is the root of your repo (i.e. `/app` in the container or `WORKDIR` if defined in a dockerfile app) - - Dokku no longer looks for this file in `/home/dokku/node-js-app` on the Dokku server - - Check out an example template [here](/docs/configuration/nginx.md) + - The default path for this custom template is the root of your repo (i.e. `/app` in the container or `WORKDIR` if defined in a dockerfile app) + - Dokku no longer looks for this file in `/home/dokku/node-js-app` on the Dokku server + - Check out an example template [here](/docs/networking/proxies/nginx.md) - Support for server-wide SSL certs have been dropped in favor of using the `certs` plugin - - `dokku certs:add node-js-app < certs.tar` + - `dokku certs:add node-js-app < certs.tar` - All domains for an SSL-enabled app will be redirected to https by default - - This can be overridden with a custom template + - This can be overridden with a custom template - Replaced "magic" `NO_VHOST` variable with `domains:enable/disable` - Simplified zero downtime control - - `checks:enable/disable` + - `checks:enable/disable` ## Dockerfile apps with exposed ports - Dockerfiles with `EXPOSE` clauses will get [all **tcp** ports proxied by default](/docs/deployment/builders/dockerfiles.md#exposed-ports) - - Note that nginx will proxy the same port numbers to listen publicly - - UDP ports can be exposed by disabling the [nginx proxy](/docs/networking/proxy-management.md) with `dokku proxy:disable node-js-app` + - Note that nginx will proxy the same port numbers to listen publicly + - UDP ports can be exposed by disabling the [nginx proxy](/docs/networking/proxy-management.md) with `dokku proxy:disable node-js-app` diff --git a/docs/appendices/0.6.0-migration-guide.md b/docs/appendices/0.6.0-migration-guide.md index 23fd4e76707..fe9cd724738 100644 --- a/docs/appendices/0.6.0-migration-guide.md +++ b/docs/appendices/0.6.0-migration-guide.md @@ -3,30 +3,30 @@ ## Zero-downtime deployment - You can now **actually** disable zero-downtime deployments per-app and per-process-type - - Some config variables have been moved around - - `DOKKU_CHECKS_ENABLED` has been migrated to `DOKKU_CHECKS_SKIPPED` - - `DOKKU_CHECKS_DISABLED` is now a thing - - The values for the above can be a comma-separated list of process-types or the literal string `_all_` - - See the [updated `checks`](/docs/deployment/zero-downtime-deploys.md) docs for more info + - Some config variables have been moved around + - `DOKKU_CHECKS_ENABLED` has been migrated to `DOKKU_CHECKS_SKIPPED` + - `DOKKU_CHECKS_DISABLED` is now a thing + - The values for the above can be a comma-separated list of process-types or the literal string `_all_` + - See the [updated `checks`](/docs/deployment/zero-downtime-deploys.md) docs for more info ## Proxy port mapping - You can now configure host -> container proxy port mappings - - The UI is handled by the [proxy](/docs/networking/proxy-management.md) interface plugin by setting the `DOKKU_PROXY_PORT_MAP` config variable in the format of `scheme:host-port:container-port`. The default `nginx-vhosts` proxy plugin supports both the `http` and `https` schemes. + - The UI is handled by the [proxy](/docs/networking/proxy-management.md) interface plugin by setting the `DOKKU_PROXY_PORT_MAP` config variable in the format of `scheme:host-port:container-port`. The default `nginx-vhosts` proxy plugin supports both the `http` and `https` schemes. - Default port mappings - - buildpack apps will be set to `http:80:5000` and will also include `https:443:5000` if SSL is enabled. - - dockerfile apps with explicitly exposed ports (i.e. using the `EXPOSE` directive) will be configured with a listener on each exposed port that proxies to the same port of the deployed application container. - - You may override this behavior with the [`proxy:ports-*` commands](/docs/networking/proxy-management.md) or by directly setting `DOKKU_PROXY_PORT_MAP` with the `config:set` command - - dockerfile apps without explicitly exposed ports will behave the same as a buildpack app - - NOTE: These defaults **are not** automatically changed on subsequent pushes and must be manipulated with the aforementioned commands + - buildpack apps will be set to `http:80:5000` and will also include `https:443:5000` if SSL is enabled. + - dockerfile apps with explicitly exposed ports (i.e. using the `EXPOSE` directive) will be configured with a listener on each exposed port that proxies to the same port of the deployed application container. + - You may override this behavior with the [`proxy:ports-*` commands](/docs/networking/proxy-management.md) or by directly setting `DOKKU_PROXY_PORT_MAP` with the `config:set` command + - dockerfile apps without explicitly exposed ports will behave the same as a buildpack app + - NOTE: These defaults **are not** automatically changed on subsequent pushes and must be manipulated with the aforementioned commands ## Calling the `dokku` binary - Plugins should *not* call the `dokku` binary directly. Clients using the `--app` argument are potentially broken, amongst others, when doing so. Instead, please source the `functions` file for a given plugin when attempting to call Dokku internal functions. As a result, the following Dokku commands are no longer publicly exposed: - - `dokku build` - - `dokku receive` - - `dokku release` - - `dokku tar:build` - - `dokku tar:build-locked` - - `dokku git:build` - - `dokku git:build-locked` + - `dokku build` + - `dokku receive` + - `dokku release` + - `dokku tar:build` + - `dokku tar:build-locked` + - `dokku git:build` + - `dokku git:build-locked` diff --git a/docs/appendices/0.8.0-migration-guide.md b/docs/appendices/0.8.0-migration-guide.md index 6247a3e5153..f0881b0d1ff 100644 --- a/docs/appendices/0.8.0-migration-guide.md +++ b/docs/appendices/0.8.0-migration-guide.md @@ -2,13 +2,13 @@ ## Domain Management -You can now set global and app domains via `domains:set` and `domains:set-global`. See the [domains documentation](/docs/configuration/domains/) for more details. +You can now set global and app domains via `domains:set` and `domains:set-global`. See the [domains documentation](/docs/configuration/domains.md) for more details. ## Plugin Uninstallation A new `uninstall` plugin trigger was introduced. This functionality may be in use for newer plugins, so be aware that older Dokku versions may require manual cleanup. -See the [uninstall trigger documentation](/docs/development/plugin-triggers/#uninstall) for implementation instructions. +See the [uninstall trigger documentation](/docs/development/plugin-triggers.md#uninstall) for implementation instructions. ## Deployment Tasks diff --git a/docs/appendices/file-formats/app-json.md b/docs/appendices/file-formats/app-json.md new file mode 100644 index 00000000000..4146aeb8225 --- /dev/null +++ b/docs/appendices/file-formats/app-json.md @@ -0,0 +1,301 @@ +# app.json + +`app.json` is a manifest format for describing web apps. It declares cron tasks, healthchecks, and other information required to run an app on Dokku. This document describes the schema in detail. + +> [!IMPORTANT] +> While the `app.json` format used by Dokku is based on the one [supported by Heroku](https://devcenter.heroku.com/articles/app-json-schema), not all Heroku functionality is supported by Dokku. + +## Buildpacks + +```json +{ + "buildpacks": [ + { + "url": "heroku/python" + }, + { + "url": "https://github.com/heroku/heroku-buildpack-nodejs.git" + } + ] +} +``` + +(list, optional) A list of buildpacks to use when deploying the app. Each entry is an object with a `url` property. Buildpacks specified via the `buildpacks:add` or `buildpacks:set` commands take precedence over those specified in `app.json`. Buildpack URLs may use the shorthand format (e.g., `heroku/python`) which will be expanded to the full GitHub URL. + +- `url`: (string, required) The URL or shorthand reference of the buildpack. + +## Cron + +```json +{ + "cron": [ + { + "command": "echo 'hello'", + "schedule": "0 1 * * *" + } + ] +} +``` + +(list, optional) A list of cron resources. Keys are the names of the process types. The values are an object containing one or more of the following properties: + +- `command`: (string, required) +- `maintenance`: (boolean, optional) +- `schedule`: (string, required) +- `concurrency_policy`: (string, optional, default: `allow`, options: `allow`, `forbid`, `replace`) + +## Env + +```json +{ + "env": { + "SIMPLE_VAR": "default_value", + "SECRET_KEY": { + "description": "A secret key for signing tokens", + "generator": "secret" + }, + "DATABASE_URL": { + "description": "PostgreSQL connection string", + "required": true + }, + "OPTIONAL_VAR": { + "description": "An optional configuration value", + "value": "default", + "required": false + }, + "SYNC_VAR": { + "description": "A variable that updates on every deploy", + "value": "synced_value", + "sync": true + } + } +} +``` + +(object, optional) A key-value object for environment variable configuration. Keys are the variable names. Values can be either a string (used as the default value) or an object with the following properties: + +- `description`: (string, optional) Human-readable explanation of the variable's purpose +- `value`: (string, optional) Default value for the variable +- `required`: (boolean, optional, default: `true`) Whether the variable must have a value +- `generator`: (string, optional) Function to generate the value. Currently only `"secret"` is supported, which generates a 64-character cryptographically secure hex string +- `sync`: (boolean, optional, default: `false`) If `true`, the value will be set on every deploy, overwriting any existing value + +### Behavior + +Environment variables from `app.json` are processed during the first deploy, before the predeploy script runs. The behavior depends on the variable configuration: + +1. **Variables with `value` or simple string**: The default value is set if the variable doesn't already exist +2. **Variables with `generator: "secret"`**: A random 64-character hex string is generated if the variable doesn't exist +3. **Required variables without a value or generator**: If a TTY is available, the user is prompted for a value. Otherwise, the deploy fails with an error +4. **Optional variables without a value**: Skipped silently if no TTY is available + +On subsequent deploys: +- Variables are NOT re-set unless `sync: true` is specified +- Variables with `sync: true` are always set to their configured value, overwriting any manual changes +- Variables that already have values are not modified + +### Examples + +**Simple default value:** +```json +{ + "env": { + "WEB_CONCURRENCY": "5" + } +} +``` + +**Generated secret (recommended for API keys, tokens, etc.):** +```json +{ + "env": { + "SECRET_KEY_BASE": { + "description": "Base secret for session encryption", + "generator": "secret" + } + } +} +``` + +**Required variable that must be provided:** +```json +{ + "env": { + "DATABASE_URL": { + "description": "PostgreSQL connection URL", + "required": true + } + } +} +``` + +**Variable that stays in sync with app.json:** +```json +{ + "env": { + "FEATURE_FLAGS": { + "value": "new_ui,dark_mode", + "sync": true + } + } +} +``` + +## Formation + +```json +{ + "formation": { + "web": { + "max_parallel": 1, + "quantity": 1 + } + } +} +``` + +(object, optional) A key-value object for process type configuration. Keys are the names of the process types. The values are an object containing one or more of the following properties: + +- `autoscaling` (map of string to object, optional) autoscaling rules. See the autoscaling section for more details +- `max_parallel`: (int, optional) number of instances to deploy in parallel at a given time +- `quantity`: (int, optional) number of processes to maintain. Default 1 for web processes, 0 for all others. +- `service`: (map of string to oject, optional) governs how non-web processes are exposed as services on the network + +### Autoscaling + +```json +{ + "formation": { + "web": { + "autoscaling": { + "cooldown_period_seconds": 300, + "max_quantity": 10, + "min_quantity": 1, + "polling_interval_seconds": 30, + "triggers": { + "http": { + "metadata": { + "url": "https://example.com/health" + } + } + } + } + } + } +} +``` + +(object, optional) A key-value object for autoscaling configuration. Keys are the names of the process types. The values are an object containing one or more of the following properties: + +- `cooldown_period_seconds`: (int, optional) +- `max_quantity`: (int, optional) +- `min_quantity`: (int, optional) +- `polling_interval_seconds`: (int, optional) +- `triggers`: (object, optional) + +An autoscaling trigger consists of the following properties: + +- `name`: (string, optional) +- `type`: (string, optional) +- `metadata`: (object, optional) + +### Service + +```json +{ + "formation": { + "internal-web": { + "service": { + "exposed": true + } + } + } +} +``` + +(object, optional) A key-value object specifying how to expose non-web processes as services. + +- `service`: (boolean, optional) Whether to expose a process as a network service. The `PORT` variable will be set to 5000. + +## Healthchecks + +```json +{ + "healthchecks": { + "web": [ + { + "type": "startup", + "name": "web check", + "description": "Checking if the app responds to the /health/ready endpoint", + "path": "/health/ready", + "attempts": 3 + } + ] + } +} +``` + +(object, optional) A key-value object specifying healthchecks to run for a given process type. + +- `attempts`: (int, optional) +- `command`: (list of strings, optional) +- `content`: (string, optional) +- `httpHeaders`: (list of header objects, optional) +- `initialDelay`: (int, optional) +- `listening`: (boolean, optional) +- `name`: (string, optional) +- `path`: (string, optional) +- `port`: (int, optional) +- `scheme`: (string, optional) +- `timeout`: (int, optional) +- `type`: (string, optional) +- `uptime`: (int, optional) +- `wait`: (int, optional) +- `warn`: (boolean, optional) +- `onFailure`: (object, optional) + +## Scripts + +```json +{ + "scripts": { + "dokku": { + "predeploy": "touch /app/predeploy.test", + "postdeploy": "curl https://some.external.api.service.com/deployment?state=success" + }, + "postdeploy": "curl https://some.external.api.service.com/created?state=success" + } +} +``` + +(object, optional) A key-value object specifying scripts or shell commands to execute at different stages in the build/release process. + +- `dokku.predeploy`: (string, optional) + - When to use: This should be used if your app does not support arbitrary build commands and you need to make changes to the built image. + - Are changes committed to the image at this phase: Yes + - Example use-cases + - Bundling assets in a slightly different way + - Installing a custom package from source or copying a binary into place +- `dokku.postdeploy`: (string, optional) + - When to use: This should be used in conjunction with external systems to signal the completion of your deploy. + - Are changes committed to the image at this phase: No + - Example use-cases + - Notifying slack that your app is deployed + - Coordinating traffic routing with a central load balancer +- `postdeploy`: (string, optional) + - When to use: This should be used when you wish to run a command _once_, after the app is created and not on subsequent deploys to the app. + - Are changes committed to the image at this phase: No + - Example use-cases + - Setting up OAuth clients and DNS + - Loading seed/test data into the app’s test database + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `app-json:report`. The JSON keys emitted by `app-json:report --format json` are the same names with the leading `--app-json-` stripped (e.g. `appjson-path`, `global-appjson-path`, `computed-appjson-path`). Legacy keys with the `app-json-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `appjson-path` | app + global | `app.json` | `--app-json-appjson-path`, `--app-json-global-appjson-path`, `--app-json-computed-appjson-path` | Path within the app to the `app.json` manifest, relative to the build root | diff --git a/docs/appendices/file-formats/buildpacks-file.md b/docs/appendices/file-formats/buildpacks-file.md new file mode 100644 index 00000000000..ac2b38548c2 --- /dev/null +++ b/docs/appendices/file-formats/buildpacks-file.md @@ -0,0 +1,14 @@ +# .buildpacks + +The `.buildpacks` file is used to specify the buildpacks for an application. It is a list of buildpack URLs. + +```shell +https://github.com/heroku/heroku-buildpack-nodejs.git +https://github.com/heroku/heroku-buildpack-ruby.git +``` + +When installed, a buildpack is checked to see if it is a git repository via `git ls-remote`. If it is not, the buildpack is checked to see if it ends in `.tgz`, `.tar.gz`, `.tbz`, `tar.bz`, or `.tar` and is downloaded/extracted before being executed. + +If the buildpack is located on Github, a shorthand can be used in the form of `organization/buildpack-name`. This will be expanded to the `https://github.com/organization/heroku-buildpack-buildpack-name.git`. The `heroku-community` organization name is treated as `heroku`. + +Comments are not allowed in the `.buildpacks` file. diff --git a/docs/appendices/file-formats/dockerfile.md b/docs/appendices/file-formats/dockerfile.md new file mode 100644 index 00000000000..59b21ccdea3 --- /dev/null +++ b/docs/appendices/file-formats/dockerfile.md @@ -0,0 +1,3 @@ +# Dockerfile + +A Dockerfile is used to build a Docker image for an application. Please refer to the [Dockerfile reference](https://docs.docker.com/reference/dockerfile/) for more information. diff --git a/docs/appendices/file-formats/lambda-yml.md b/docs/appendices/file-formats/lambda-yml.md new file mode 100644 index 00000000000..f390fe09ff1 --- /dev/null +++ b/docs/appendices/file-formats/lambda-yml.md @@ -0,0 +1,16 @@ +# lambda.yml + +The `lambda.yml` file is used to configure a Lambda application. + +```yaml +--- +build_image: mlupin/docker-lambda:dotnetcore3.1-build +builder: dotnet +run_image: mlupin/docker-lambda:dotnetcore3.1 +``` + +## Fields + +`build_image`: A docker image that is accessible by the docker daemon. The `build_image` should be based on an existing Lambda image - builders may fail if they cannot run within the specified `build_image`. The build will fail if the image is inaccessible by the docker daemon. +`builder`: The name of a builder. This may be used if multiple builders match and a specific builder is desired. If an invalid builder is specified, the build will fail. +`run_image`: A docker image that is accessible by the docker daemon. The `run_image` should be based on an existing Lambda image - built images may fail to start if they are not compatible with the produced artifact. The generation of the `run_image` will fail if the image is inaccessible by the docker daemon. diff --git a/docs/appendices/file-formats/nginx-conf-sigil.md b/docs/appendices/file-formats/nginx-conf-sigil.md new file mode 100644 index 00000000000..3de3fdbf64f --- /dev/null +++ b/docs/appendices/file-formats/nginx-conf-sigil.md @@ -0,0 +1,65 @@ +# nginx.conf.sigil + +The `nginx.conf.sigil` file is used to configure the nginx server for an application. The default template can be found [here](https://github.com/dokku/dokku/blob/master/plugins/nginx-vhosts/templates/nginx.conf.sigil). Dokku uses a tool named [sigil](https://github.com/gliderlabs/sigil) to generate the nginx configuration based on the template provided. + +## Validation + +A custom `nginx.conf.sigil` is pre-validated at the start of every deploy, immediately after it is extracted from the source tree and before the build phase runs. Pre-validation renders the template via sigil with the same parameters used at deploy time, wraps the rendered config in a minimal `events`/`http` scaffold, and runs `nginx -t` against the result. The deploy is aborted if either the sigil render or the `nginx -t` check fails, so build work is not wasted on a syntactically invalid template. When no app listeners exist yet (typical for first deploys), pre-validation injects a placeholder `127.0.0.1:5000` listener for `DOKKU_APP_WEB_LISTENERS` so that the rendered upstream block has a static server entry and `nginx -t` does not bail out on "host not found in upstream". + +Pre-validation is skipped when the proxy type is not `nginx` or when `disable-custom-config` is set to `true` for the app. + +### Custom nginx modules + +Pre-validation runs `nginx -t` against a minimal wrapper config that does _not_ include the top-level `load_module` directives from the global `/etc/nginx/nginx.conf`. A `nginx.conf.sigil` that uses a directive provided by a dynamically loaded module - such as `image_filter`, provided by the [ngx_http_image_filter_module](https://nginx.org/en/docs/http/ngx_http_image_filter_module.html) - therefore fails pre-validation with an `unknown directive` error, even though `nginx -t` succeeds against the real server config where the module is loaded. + +The `load_module` directive cannot be added to the app's `nginx.conf.sigil` to work around this, as that file is included inside the `http { }` block while `load_module` is only valid in nginx's top-level main context. + +To make pre-validation aware of a module, override the wrapper template used for validation. The [`nginx-app-template-source`](/docs/development/plugin-triggers.md#nginx-app-template-source) trigger returns the path to the `sigil` template used to generate a given nginx configuration file, and its `validate-config` template type controls the pre-validation wrapper. Create a [custom plugin](/docs/development/plugin-creation.md) that implements the trigger and returns a `validate.conf.sigil` that adds the required `load_module` line at the top of the wrapper. + +The trigger file (named `nginx-app-template-source` and marked executable): + +```shell +#!/usr/bin/env bash + +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +APP="$1" +TEMPLATE_TYPE="$2" +if [[ "$TEMPLATE_TYPE" == "validate-config" ]]; then + echo "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/validate.conf.sigil" +fi +``` + +The custom `validate.conf.sigil`, which is the [default wrapper](https://github.com/dokku/dokku/blob/master/plugins/nginx-vhosts/templates/validate.conf.sigil) with the required `load_module` line added at the top. Use the same `load_module` line that the host's global `/etc/nginx/nginx.conf` uses: + +``` +load_module modules/ngx_http_image_filter_module.so; +events { worker_connections 768; } +http { + access_log off; + error_log /dev/null; + include {{ $.NGINX_CONF }}; +} +``` + +The same override also governs the standalone `dokku nginx:validate-config` command, which renders the `validate-config` template as well. + +## HTTP/2 + +nginx 1.25.1 deprecated the `http2` parameter on the `listen` directive in favor of a standalone `http2 on;` directive. Custom `nginx.conf.sigil` templates that hardcode `listen ... ssl http2;` will produce `nginx: [warn] the "listen ... http2" directive is deprecated` warnings when run against nginx 1.25.1 or newer. + +Dokku exposes the `HTTP2_DIRECTIVE_SUPPORTED` template variable, set to `"true"` when the host nginx is 1.25.1 or newer, so a single template can render the correct syntax against either version. The default template uses this pattern: + +``` +{{ if eq $.HTTP2_DIRECTIVE_SUPPORTED "true" }} +listen [{{ $.NGINX_BIND_ADDRESS_IP6 }}]:{{ $listen_port }} ssl; +listen {{ if $.NGINX_BIND_ADDRESS_IP4 }}{{ $.NGINX_BIND_ADDRESS_IP4 }}:{{end}}{{ $listen_port }} ssl; +http2 on; +{{ else }} +listen [{{ $.NGINX_BIND_ADDRESS_IP6 }}]:{{ $listen_port }} ssl http2; +listen {{ if $.NGINX_BIND_ADDRESS_IP4 }}{{ $.NGINX_BIND_ADDRESS_IP4 }}:{{end}}{{ $listen_port }} ssl http2; +{{ end }} +``` + +Dokku logs a deprecation warning during deploys when a custom template still uses the `listen ... http2` form, so the offending template can be located via the deploy output. diff --git a/docs/appendices/file-formats/nixpacks-toml.md b/docs/appendices/file-formats/nixpacks-toml.md new file mode 100644 index 00000000000..3d1baff00ba --- /dev/null +++ b/docs/appendices/file-formats/nixpacks-toml.md @@ -0,0 +1,3 @@ +# nixpacks.toml + +The `nixpacks.toml` file is used to configure an application when built with the `nixpacks` builder. Please refer to the [nixpacks.toml documentation](https://nixpacks.com/docs/configuration/file.md) for more information. diff --git a/docs/appendices/file-formats/procfile.md b/docs/appendices/file-formats/procfile.md new file mode 100644 index 00000000000..270988f58d9 --- /dev/null +++ b/docs/appendices/file-formats/procfile.md @@ -0,0 +1,48 @@ +# Procfile + +A Procfile is a file that was [promoted by Heroku](https://blog.heroku.com/the_new_heroku_1_process_model_procfile) for their platform as an easy way to specify one or more distinct processes to run within Heroku. This format has since been picked up by various other tools and platforms. + +## General Overview + +The `procfile-util` tool expects a Procfile to be defined as one or more lines containing one of: + +- a comment (preceeded by a `#` symbol or two `//` characters) +- a process-type/command combination (with optional trailing whitespace or trailing comment) + - when there is a trailing comment, the `#` symbol/`//` characters _must_ be preceeded by one or more `whitespace` characters. +- a blank line (with optional trailing whitespace) + +Comments and blank lines are ignored, while process-type/command combinations look like the following: + +``` +: +``` + +The syntax is defined as follows: + +- `` – any character in the class `[A-Za-z0-9_-]+`, a process type is a name for your command, such as `web`, `worker`, `urgentworker`, `clock`, etc. +- `` – a command used to launch the process, such as `rake jobs:work` + +Additional rules are as follows: + +- contain at most 63 characters + +A Procfile that does not match any of the above rules will be considered invalid, and not be processed. This is to avoid issues where a Procfile may contain merge conflicts or other improper content, thus resulting in unwanted runtime behavior for applications. + +Finally, process types within a Procfile may not overlap and must be unique. Rather than assuming that the last or first specified is correct, `procfile-util` will fail to parse the Procfile with the relevant error. + +### Strict Mode + +> Strict mode can be triggered on `procfile-util` via the `--strict` flag. + +In strict mode, the character set of a process type changes. + +- `` – a valid DNS Label Name as per [RFC 1123](https://tools.ietf.org/html/rfc1123), a process type is a name for your command, such as `web`, `worker`, `urgentworker`, `clock`, etc. + +This syntax differs common interpretations of valid `` values in that we define the process type as a DNS Label name, versus the regex `[A-Za-z0-9_]+`. The reason for this is that processes defined within Procfiles are commonly used in DNS entries. Rather than have a second level of platform-specific validation in place, this project implicitly defines the format for the process-type. + +Given the above, a valid process type can be generalized to the following rules (as taken from the [Kubernetes documentation](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names)): + +- contain at most 63 characters +- contain only lowercase alphanumeric characters or '-' +- start with an alphanumeric character +- end with an alphanumeric character diff --git a/docs/appendices/file-formats/project-toml.md b/docs/appendices/file-formats/project-toml.md new file mode 100644 index 00000000000..f5411a75cea --- /dev/null +++ b/docs/appendices/file-formats/project-toml.md @@ -0,0 +1,3 @@ +# project.toml + +The project descriptor file allows app developers to provide configuration for apps deployed to Dokku via the `pack` builder. Please refer to the [project.toml documentation](https://buildpacks.io/docs/reference/config/project-descriptor/) for more information. diff --git a/docs/appendices/file-formats/railpack-json.md b/docs/appendices/file-formats/railpack-json.md new file mode 100644 index 00000000000..d6e390fb410 --- /dev/null +++ b/docs/appendices/file-formats/railpack-json.md @@ -0,0 +1,3 @@ +# railpack.json + +The `railpack.json` file is used to configure an application when built with the `railpack` builder. Please refer to the [railpack.json documentation](https://railpack.com/config/file) for more information. diff --git a/docs/assets/dokku-logo.svg b/docs/assets/dokku-logo.svg new file mode 100644 index 00000000000..29ada9ae083 --- /dev/null +++ b/docs/assets/dokku-logo.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/extra.css b/docs/assets/extra.css new file mode 100644 index 00000000000..c9ff785f566 --- /dev/null +++ b/docs/assets/extra.css @@ -0,0 +1,23 @@ +:root > * { + --md-primary-fg-color: #2A8FBD; + --md-primary-fg-color--light: #ECB7B7; + --md-primary-fg-color--dark: #90030C; +} + +.md-typeset .mdx-badge { + font-size: .80em; +} + +.md-typeset .mdx-badge__icon { + background: var(--md-accent-fg-color--transparent); + padding: 0.2rem; + border-top-left-radius: 0.1rem; + border-bottom-left-radius: 0.1rem +} + +.md-typeset .mdx-badge__text { + box-shadow: 0 0 0 1px inset var(--md-accent-fg-color--transparent); + padding: 0.2rem 0.3rem; + border-bottom-right-radius: 0.1rem; + border-top-right-radius: 0.1rem; +} diff --git a/docs/assets/favicons/browserconfig.xml b/docs/assets/favicons/browserconfig.xml index 490d70f88e4..16326705dc8 100644 --- a/docs/assets/favicons/browserconfig.xml +++ b/docs/assets/favicons/browserconfig.xml @@ -2,10 +2,10 @@ - - - - + + + + #da532c diff --git a/docs/assets/favicons/manifest.json b/docs/assets/favicons/manifest.json index 5420399351c..2b8bdc40db9 100644 --- a/docs/assets/favicons/manifest.json +++ b/docs/assets/favicons/manifest.json @@ -2,37 +2,37 @@ "name": "Dokku", "icons": [ { - "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.26.8\/docs\/assets\/favicons\/android-chrome-36x36.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.38.25\/docs\/assets\/favicons\/android-chrome-36x36.png", "sizes": "36x36", "type": "image\/png", "density": "0.75" }, { - "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.26.8\/docs\/assets\/favicons\/android-chrome-48x48.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.38.25\/docs\/assets\/favicons\/android-chrome-48x48.png", "sizes": "48x48", "type": "image\/png", "density": "1.0" }, { - "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.26.8\/docs\/assets\/favicons\/android-chrome-72x72.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.38.25\/docs\/assets\/favicons\/android-chrome-72x72.png", "sizes": "72x72", "type": "image\/png", "density": "1.5" }, { - "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.26.8\/docs\/assets\/favicons\/android-chrome-96x96.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.38.25\/docs\/assets\/favicons\/android-chrome-96x96.png", "sizes": "96x96", "type": "image\/png", "density": "2.0" }, { - "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.26.8\/docs\/assets\/favicons\/android-chrome-144x144.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.38.25\/docs\/assets\/favicons\/android-chrome-144x144.png", "sizes": "144x144", "type": "image\/png", "density": "3.0" }, { - "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.26.8\/docs\/assets\/favicons\/android-chrome-192x192.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.38.25\/docs\/assets\/favicons\/android-chrome-192x192.png", "sizes": "192x192", "type": "image\/png", "density": "4.0" diff --git a/docs/assets/placeholder-avatar.jpg b/docs/assets/placeholder-avatar.jpg new file mode 100644 index 00000000000..26c0e491876 Binary files /dev/null and b/docs/assets/placeholder-avatar.jpg differ diff --git a/docs/assets/social/discord.png b/docs/assets/social/discord.png new file mode 100644 index 00000000000..57a4ff53f44 Binary files /dev/null and b/docs/assets/social/discord.png differ diff --git a/docs/assets/social/github.png b/docs/assets/social/github.png new file mode 100644 index 00000000000..f3959d46bc7 Binary files /dev/null and b/docs/assets/social/github.png differ diff --git a/docs/assets/social/irc.png b/docs/assets/social/irc.png new file mode 100644 index 00000000000..cfe5aa54af6 Binary files /dev/null and b/docs/assets/social/irc.png differ diff --git a/docs/assets/social/slack.png b/docs/assets/social/slack.png new file mode 100644 index 00000000000..ca910f44cde Binary files /dev/null and b/docs/assets/social/slack.png differ diff --git a/docs/assets/style.css b/docs/assets/style.css index 3153156c17b..402f14c45f9 100644 --- a/docs/assets/style.css +++ b/docs/assets/style.css @@ -1,251 +1,687 @@ :root { - color-scheme: light dark; + /* Used on the hero background */ + --primary-blue: #2A8FBD; + --dropdown-hover: #277FA930; + --cta-color: #79ACCA; + --cta-hover-color: #97C0D9; + --text-blue: #297BAB; + --badge-color: #09CACE5C; + --badge-circle-color: #297BAC; + --badge-hover-color: #10E3E85C; + --explore-cta-hover: #ffffffe8; + /* Used on anchor tag on blue background */ + --transparent-white: #fffc; + /* Used for section background */ + --slate-bg: #F5F5F5; + --slate-border: #DADADA; + --animation-speed: 40s; + /* Used on the code showcase */ + --gradient-shadow: linear-gradient(45deg, #2A8FBD, #679ab2, #ffffff, #81ccef, #2abbbd, #c9e0eb, #66c1eb, #40809e, #2A8FBD); + + --darkmode-light: #11398c; + --darkmode-button-light: #325fae; + --darkmode-button-light-hover: #254989; + --darkmode-dark: #0a2d75; + + /* Avatar section darkmode fade */ + --avatar-before-gradient: rgba(0, 0, 0, 0) linear-gradient(90deg, white 2.52%, rgba(255, 255, 255, 0) 100%) repeat scroll 0% 0%; + --avatar-darkmode-before-gradient: rgba(0, 0, 0, 0) linear-gradient(90deg, #0.38.25 2.52%, rgba(255, 255, 255, 0) 100%) repeat scroll 0% 0%; + --avatar-after-gradient: rgba(0, 0, 0, 0) linear-gradient(270deg, white 2.52%, rgba(255, 255, 255, 0) 100%) repeat scroll 0% 0%; + --avatar-darkmode-after-gradient: rgba(0, 0, 0, 0) linear-gradient(270deg, #0.38.25 2.52%, rgba(255, 255, 255, 0) 100%) repeat scroll 0% 0%; } + body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - font-size: 62.5%; - font-weight: 400; - padding-bottom: 40px; - color: #5a5a5a; - -webkit-font-smoothing: antialiased; + font-family: 'Lato', sans-serif; } -h1 { - color: #5a5a5a; - font-weight: 400; - line-height: 1.1; - margin-bottom: 12px; - margin-top: 12px; - text-rendering: optimizelegibility; + +.btn-check:active+.btn:focus, +.btn-check:checked+.btn:focus, +.btn.active:focus, +.btn.show:focus, +.btn:active:focus { + box-shadow: none; } -.header { - background-color: #2a8fbd; - padding: 22px 0; + +.fit-content { + width: fit-content; } -.header .heading { - color: #fff; - font-size: 2.4em; - margin: 7px 0 0 0; - padding: 0 0 0 1em; + + +/* Common */ + +.cta-arrow { + position: relative; + margin-left: 5px; + stroke-width: 2px; + fill: none; + stroke: currentColor; } -.header a { - color: #fff; - background-position: center left; - display: block; - width: 100%; - height: 100%; - -webkit-background-size: contain; - -moz-background-size: contain; - background-size: contain; - background-repeat: no-repeat; + +.cta-arrow .cta-arrow__tip { + transition: transform .2s; } -.header .nav-item a { - font-size: 1.6em; + +.cta-arrow .cta-arrow__line { + opacity: 0; + transition: opacity .2s; } -.header .navbar-brand a { - background-image: url(https://cdn.jsdelivr.net/gh/dokku/dokku@v0.26.8/docs/assets/dokku.png); - text-indent: 40px; + +a:hover>.cta-arrow .cta-arrow__line { + opacity: 1; } -.blurb { - color: #424242; - background-color: #ededed; - background-image: url(https://cdn.jsdelivr.net/gh/dokku/dokku@v0.26.8/docs/assets/gplaypattern.png); - padding: 45px 0; - text-align: center; + +a:hover>.cta-arrow .cta-arrow__tip { + transform: translateX(3px); +} + + +/* Navigation */ + +.navbar-toggler, +.navbar-toggler:focus { + border: none; + box-shadow: none; + } -.blurb h2 { - font-size: 3.5em; + +.navbar-brand { + color: white; + font-weight: bold; } -.blurb p { - font-size: 1.9em; - font-weight: 300; - margin: 18px 0; - text-align: center; + +.navbar-brand:hover { + color: white; +} + +.nav-item .btn:hover { + border-color: transparent; + background-color: var(--dropdown-hover); } -.blurb .btn-lg { - font-size: 2.2em; + +.nav-item .dropdown-menu strong { + width: max-content; } + +.nav-item .dropdown-menu a, +.nav-item .dropdown-menu a:hover { + color: var(--text-blue); +} + +.nav-item a { + font-weight: bolder; + color: var(--transparent-white); + transition: all .2s; +} + +.nav-item a:hover, +.nav-item a:focus { + color: white; +} + +a.dropdown-toggle.show { + color: white; +} + +.nav-item .cta, +.nav-item .cta:focus { + background-color: var(--cta-color); + box-shadow: none; + border: none; + color: white; +} + +.nav-item .cta:hover { + background-color: var(--cta-hover-color); + color: white; +} + +.dropdown-menu.show { + box-shadow: 0px 0px 5px 0px #a1e1ff; + border: none; + margin-top: 10px; +} + +ul.navbar-nav li.nav-item { + height: max-content; + transition: transform .2s; +} + + +/* hero section */ + +.hero-section { + background: radial-gradient(56.99% 613.79% at 24.22% 51.49%, rgba(40, 39, 107, 0.2) 0%, rgba(0, 0, 0, 0.098) 100%), linear-gradient(0deg, #2A8FBD, #2A8FBD), linear-gradient(0deg, #28276C, #28276C); +} + +.hero-section .badge-link { + font-weight: normal; + color: white; + text-decoration: none; + display: inline-block; +} + +.hero-section .badge { + background-color: var(--badge-color); + font-size: 16px; + transition: background-color .2s; +} + +.hero-section .badge-link:hover .badge { + background-color: var(--badge-hover-color); +} + +.hero-section .cta { + background-color: var(--cta-color); + border: none; +} + +.hero-section .cta:hover { + background-color: var(--cta-hover-color); +} + +.hero-section .badge .circle { + width: 8px; + height: 8px; + display: inline-block; + background: var(--badge-circle-color); + border-radius: 100%; +} + +.hero-heading { + font-weight: bolder; + font-size: 42px; +} + +.hero-section .explore-cta { + background-color: white; + color: var(--primary-blue); +} + +.hero-section .explore-cta:hover { + background-color: var(--explore-cta-hover); + color: var(--primary-blue); +} + +.hero-section main { + padding-top: 5em; + padding-bottom: 5em; +} + +.hero-section code { + background-color: var(--badge-color); + color: white; + font-family: inherit; + border-radius: 2px; +} + +a.platform { + font-weight: 900; + color: inherit; + opacity: .8; + transition: all .2s; + text-decoration: none; +} + +a.platform:hover { + color: inherit; + opacity: 1; +} + + +/* Quickstart code */ + .quickstart-code { - display: block; + position: relative; line-height: 1.3em; - margin: 45px auto; - max-width: 860px; + filter: drop-shadow(0px 0px 8px #3B3D84); padding: 0; } + +.quickstart-code:before, +.quickstart-code:after { + content: ""; + position: absolute; + border-radius: 5px; + top: -2px; + left: -2px; + background: var(--gradient-shadow); + background-size: 400%; + width: calc(100% + 4px); + height: calc(100% + 4px); + z-index: -1; + animation: animate 60s linear infinite; +} + +.quickstart-code:after { + filter: blur(30px); +} + +@keyframes animate { + 0% { + background-position: 0 0; + } + + 50% { + background-position: 300% 0; + } + + 100% { + background-position: 0 0; + } +} + .quickstart-code .title { background-color: #dfdfdf; background-image: linear-gradient(top, #f7f7f7 0%, #dfdfdf 7%, #ccc 100%); border-radius: 5px 5px 0 0; - box-shadow: 0 3px 0 rgba(0, 0, 0, 0.5); color: #444; display: block; - font-size: 1.6em; + font-size: 1em; font-weight: 400; - margin: 0 20px; + margin: 0px; padding: 3px 0; text-align: center; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); } -.quickstart-code .tabs { - background-color: #bcbcbc; - border-top: 1px solid #686868; - color: #444; - display: flex; - display: -webkit-flex; - font-weight: bold; - margin: 0 20px; - padding: 0; - text-align: center; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} -.quickstart-code .tab { - border-right: 1px solid #686868; - cursor: pointer; - flex: 1; - -webkit-flex: 1; -} -.quickstart-code .tab-active { - background-color: #ededed; - border-radius: 0 0 5px 5px; - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.5); -} -.quickstart-code .tab:last-of-type { - border-right: none; -} + .quickstart-code .shell { - background-color: #171717; + background-color: #555699; border-radius: 0 0 5px 5px; border-top: 1px solid #bcbcbc; - box-shadow: 0 5px 30px rgba(0, 0, 0, 0.3); - display: none; - font-size: 1.4em; + display: block; + font-size: 1em; font-family: Menlo, Consolas, "Courier New", Courier, "Liberation Mono", monospace; - margin: 0 20px; + margin: 0px; padding: 20px; text-shadow: none; } -.quickstart-code .shell-active { - display: block; -} + .quickstart-code .line { display: block; margin: 0; padding: 0; } -.quickstart-code .line span { - display: inline-block; -} + .quickstart-code .line .output { - color: #aaa; -} -.quickstart-code .path { - color: #2a8fbd; - content: '~'; - user-select: none; + color: #b7e2f9; } + .quickstart-code .prompt { color: #f90; user-select: none; } + .quickstart-code .command { color: #ffc; + line-break: anywhere; } -.alternate-instructions { - display: block; - font-size: 1.6em; - line-height: 1.3em; - margin: 45px auto; - max-width: 860px; + + +/* Powered by */ + +.powered-by { + padding-top: 8rem; + padding-bottom: 8rem; +} + +.copy-caption { + color: var(--primary-blue); + border-left: 5px solid var(--primary-blue); +} + +.copy-title { + font-size: 38px; +} + +.copy-body { + line-height: 28px; + font-size: 18px; +} + +.copy-body p { + margin-bottom: 1.3em; +} + +a.copy-cta-btn { + color: var(--primary-blue); + background-color: transparent; + border-color: transparent; + border: none; padding: 0; - text-align: center; + vertical-align: baseline; } -.marketing .col-lg-4 { - margin-bottom: 20px; - text-align: center; + +a.copy-cta-btn:hover { + color: var(--primary-blue); + background-color: transparent; + border: transparent; +} + +.powered-by_brands img { + max-width: 300px; +} + + +/* Getting started */ +.getting-started { + margin-bottom: -20%; + z-index: 1; +} + +.getting-started .container { + position: relative; + z-index: 1; } -.marketing .col-lg-4 p { + +.getting-started::after { + position: absolute; + content: ""; + top: 30%; + left: 0; + right: 0; + bottom: 20%; + background: linear-gradient(0deg, rgba(52, 52, 87, 0.2) -40.97%, rgba(0, 0, 0, 0) 103.19%), #2781AB; + z-index: 0; +} + +.nav-tabs { + width: fit-content; + margin: 0 auto; + padding: 10px; + background-color: #4794B8; + border-radius: 20px 20px 0px 0px; +} + +.nav-tabs .nav-link { + background: rgba(85, 86, 153, 0.72); + border-radius: 0px; + padding: 0.6em 3em; + color: var(--transparent-white); + font-weight: bold; + border: 0px; + position: relative; + font-size: 1em; +} + +.nav-tabs .nav-link:hover { + color: white; +} + +.nav-tabs .nav-link:hover svg path { + fill: white; +} + +.nav-tabs .nav-link svg { margin-right: 10px; - margin-left: 10px; } -.featurette-divider { - margin: 20px 0; + +.nav-tabs .nav-link svg path { + fill: var(--bs-gray-300); + transition: all .2s } -.featurette-heading { - font-weight: normal; - line-height: 1; - letter-spacing: -1px; + +.nav-tabs .nav-link.active { + background: linear-gradient(0deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), #555699; + color: white; } -.featurette-heading .text-muted { - font-weight: 300; + +.nav-tabs .nav-link.active svg path { + fill: white; } -.featurette img { - width: 400px; + +.nav-tabs .nav-link.active::after { + content: ''; + position: absolute; + margin-left: -10px; + left: 50%; + bottom: 0px; + height: 0; + width: 0; + border-bottom: 10px solid #4794B8; + border-left: 10px solid transparent; + border-right: 10px solid transparent; } -.slack-channel, -.sponsors { - background-color: #EEF1F7; - font-size: 1.6em; - font-weight: 300; - margin-bottom: 2.5em; - padding-top: 2.5em; - padding-bottom: 2.5em; - text-align: center + +.nav-tabs .nav-link:nth-child(1) { + border-radius: 15px 0px 0px 0px; } -.slack-channel .inline-container { - display: inline-block; - margin-left: 0.5em + +.nav-tabs .nav-link:nth-child(3) { + border-radius: 0px 15px 0px 0px; +} + +.tab-content { + margin: 0 auto; + margin-top: -10px; + background: #4794B8; + box-shadow: 0px 0px 30px rgba(0, 0, 0, 0.25); + border-radius: 7px; + padding: 10px; + padding-bottom: 0px; } -.slack-button { - background-image: -webkit-linear-gradient(#FCFCFC, #EAEAEA); - background-image: linear-gradient(#FCFCFC, #EAEAEA); - border: 1px solid #ddd; - padding: 0.25rem 0.5rem; + +.tab-pane { + margin-top: -20px; } -.slack-button img { - height: 1em; + +.tab-pane img { + width: 100%; + padding-top: 20px; + padding-bottom: 20px; } -.sponsors { - background-color: #272822; - color: #FCFCFC; + +/* Plugins section */ + +section.plugins { + padding-top: 25%; + padding-bottom: 8rem; + background: var(--slate-bg); + border-width: 2px 2px; + border-style: solid; + border-color: var(--slate-border); } -.sponsors a { - color: #f0ad4e; + +section.plugins svg { + transform: scale(1.3); } -.sponsors .backer img, -.sponsors .sponsor img { - -webkit-border-radius: 3px; - border-radius: 3px; - border: 8px solid gray; - margin: 20px auto 20px auto; - max-width: 192px; + +g#plugins:hover, +g#plugins:hover g { + -webkit-animation-play-state: paused !important; + -moz-animation-play-state: paused !important; + -o-animation-play-state: paused !important; + animation-play-state: paused !important; +} + + +/* Support Us */ + +section.supportus { + padding-top: 8rem; + padding-bottom: 8rem; +} + +.supportus .copy-body { + max-width: 800px; + margin: 0 auto; +} + +.supportus a.opencollective { + color: var(--primary-blue); + text-decoration: none; + font-weight: bold; } -.sponsors .backer img { - -webkit-border-radius: 50%; + +.avatar-section { + margin-top: 5em; + overflow: hidden; + position: relative; +} + +.avatar-section:before, +.avatar-section:after { + content: ""; + position: absolute; + top: 0px; + width: 12%; + height: 100%; + pointer-events: none; + z-index: 2; +} + +.avatar-section:before { + left: 0px; + background: var(--avatar-before-gradient); +} + +.avatar-section:after { + right: 0px; + background: var(--avatar-after-gradient); +} + +.avatar-row { + width: max-content; +} + +.infinite-scroll { + animation: 48s linear 0s infinite normal none running scroll; +} + +.avatar-row:hover { + -webkit-animation-play-state: paused; + -moz-animation-play-state: paused; + -o-animation-play-state: paused; + animation-play-state: paused; +} + +.avatar-row:nth-child(2) { + animation-duration: 60s; +} + +.avatar { + width: 80px; + height: 80px; border-radius: 50%; - border: 0; + display: flex; + justify-content: center; + overflow: hidden; + flex-shrink: 0; } -.sponsors img:hover { - -moz-box-shadow: 4px 4px 8px 0px rgba(0,0,0,0.75); - -webkit-box-shadow: 4px 4px 8px 0px rgba(0,0,0,0.75); - box-shadow: 0px 0px 32px 0px rgba(0,0,0,0.75); + +.avatar img { + flex-shrink: 0; + min-width: 100%; + min-height: 100% } -.sponsors .fund-link { - display: block; - font-size: .8em; + +.avatar-link { + padding: 5px; + margin-right: 20px; + margin-bottom: 20px; + background: white; + border-radius: 50%; + filter: drop-shadow(rgba(0, 0, 0, 0.1) 0px 4px 6px); + transition: transform .2s linear; +} + +.avatar-link:hover { + transform: scaleX(1.1) scaleY(1.1); } + +@keyframes scroll { + from { + transform: translateX(0%); + } + + to { + transform: translateX(-50%); + } +} + + +/* pro-cta section */ + +section.pro-cta { + background: var(--slate-bg); + border-width: 2px 2px; + border-style: solid; + border-color: var(--slate-border); +} + + +/* Footer */ + +footer { + background: var(--primary-blue); +} + +footer p { + font-size: 1em; +} + +footer a { + color: inherit; + font-weight: 900; + opacity: .9; + transition: all .2s; + text-decoration: none; +} + +footer a:hover { + color: inherit; + opacity: 1; +} + + +/* Accessibility */ + +@media (prefers-reduced-motion) { + + g#plugins, + g#plugins g, + .avatar-row, + .quickstart-code:before, + .quickstart-code:after { + -webkit-animation-play-state: paused !important; + -moz-animation-play-state: paused !important; + -o-animation-play-state: paused !important; + animation-play-state: paused !important; + } +} + +/* Documentation */ .list-group-item { border: none; font-size: 14px; font-weight: normal; padding: .2rem .5rem; color: #157577; + transition: all .1s; } + a.list-group-item { - color: #666 + color: #666; + font-size: 1em; +} + +a.list-group-item:focus, +a.list-group-item:hover, +button.list-group-item:focus, +button.list-group-item:hover { + color: #555; + text-decoration: none; + background-color: #f5f5f5; } -.list-group-item.disabled:first-child, .list-group-item.disabled:first-child:focus, .list-group-item.disabled:first-child:hover { + +.list-group-item.disabled:first-child, +.list-group-item.disabled:first-child:focus, +.list-group-item.disabled:first-child:hover { margin-top: 0; } -.list-group-item.disabled, .list-group-item.disabled:focus, .list-group-item.disabled:hover { + +.list-group-item.disabled, +.list-group-item.disabled:focus, +.list-group-item.disabled:hover { background: none; color: #bbb; cursor: default; @@ -254,28 +690,32 @@ a.list-group-item { margin-top: 2em; text-transform: uppercase; } + .table-of-contents { font-size: 1.2em; padding: 1em; margin: 0 0 0.5em 0.5em; } + .table-of-contents ul { margin-bottom: 0; padding-left: 20px; } + .anchorjs-link { color: #24cbce; } + .markdown-body { -webkit-font-smoothing: antialiased; box-sizing: border-box; color: rgb(71, 74, 84); - font-size: 1.6em; + font-size: 1.1em; font-weight: normal; - height: 20px; - line-height: 1.5; + line-height: 1.7; text-align: left; } + .markdown-body h1 { border-bottom: 1px solid #dfdfdf; color: #444; @@ -283,32 +723,40 @@ a.list-group-item { padding-bottom: 1em; margin-top: 0; text-rendering: optimizelegibility; + font-weight: 900; } + .markdown-body h2 { font-size: 1.3em; font-weight: 700; color: #555; margin-top: 2em; text-transform: uppercase; + font-weight: 900; } + .markdown-body h3 { font-size: 1.2em; font-weight: 700; color: #555; margin: 1em 0; } + .markdown-body h4 { font-size: 1.1em; font-weight: 700; color: #555; margin: 1em 0; } -.markdown-body h1 + h2 { + +.markdown-body h1+h2 { margin-top: 1em; } + .highlight-show-language { position: relative; } + .highlight-show-language-label { color: black; background-color: #CFCFCF; @@ -334,11 +782,13 @@ a.list-group-item { -o-transform: none; transform: none; } + .highlight-output pre { color: #fff; background-color: #000; padding: 10px; } + code { padding: 0; padding-top: 0.2em; @@ -351,18 +801,23 @@ code { border-radius: 3px; font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; } + code:before { letter-spacing: -0.2em; content: "\00a0"; } + code:after { letter-spacing: -0.2em; content: "\00a0"; } -pre code:before, pre code:after { + +pre code:before, +pre code:after { letter-spacing: 0; content: ""; } + pre { display: block; line-height: 1.428571429; @@ -377,38 +832,48 @@ pre { color: #4d4d4c; margin: 2em 0; } + blockquote { border-left: 4px solid #5bc0de; background-color: #f4f8fa; padding: 15px 15px 1px; margin-bottom: 30px; } + .new-as-of { border-left: 4px solid #d1f2a5; background-color: #effab4; } + .not-yet-released { border-left: 4px solid #ef5b58; background-color: #f9ad76; } + .warning { border-left: 4px solid #cd2512; background-color: #d1675f; color: white; } + .fa:before { -webkit-font-smoothing: antialiased } + .clearfix { *zoom: 1 } -.clearfix:before, .clearfix:after { + +.clearfix:before, +.clearfix:after { display: table; content: "" } + .clearfix:after { clear: both } + .improve-slideout { position: fixed; bottom: 66%; @@ -418,15 +883,18 @@ blockquote { -o-transition-duration: 0.3s; transition-duration: 0.3s; background: #363637; - padding: 5px 0 4px; + padding: 0px 0 3px; z-index: 90; } + .improve-slideout:hover { right: 205px; } + .improve-slideout:hover .improve-slideout-inner { right: 0; } + .improve-slideout-inner { position: fixed; bottom: 66%; @@ -439,17 +907,20 @@ blockquote { padding: 4px 12px; width: 205px; } + .improve-slideout-inner h6 { color: #363637; font-weight: 700; text-transform: uppercase; margin: 0; - font-size: 1.3em; + font-size: 1em; } + .git-improve { - vertical-align: -2px; + vertical-align: baseline; padding-left: 8px; } + .back-to-contents { position: fixed; bottom: calc(66% - 34px); @@ -458,31 +929,41 @@ blockquote { padding: 5px 0 4px; z-index: 90; } + .icon-improve { color: #bdbdb5; padding: 2px 9px 0 10px; } + a .fa { display: inline-block; text-decoration: inherit } + li .fa { display: inline-block } -li .fa-large:before, li .fa-large:before { + +li .fa-large:before, +li .fa-large:before { width: 1.875em } + ul.fas { list-style-type: none; margin-left: 2em; text-indent: -0.8em } + ul.fas li .fa { width: 0.8em } -ul.fas li .fa-large:before, ul.fas li .fa-large:before { + +ul.fas li .fa-large:before, +ul.fas li .fa-large:before { vertical-align: baseline } + .rst-versions { position: fixed; bottom: 0; @@ -494,13 +975,16 @@ ul.fas li .fa-large:before, ul.fas li .fa-large:before { font-family: "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif; z-index: 400 } + .rst-versions a { color: #7c8e98; text-decoration: none } + .rst-versions .rst-badge-small { display: none } + .rst-versions .rst-current-version { padding: 12px; background-color: #2475c3; @@ -510,31 +994,40 @@ ul.fas li .fa-large:before, ul.fas li .fa-large:before { color: #d8edf8; *zoom: 1 } -.rst-versions .rst-current-version:before, .rst-versions .rst-current-version:after { + +.rst-versions .rst-current-version:before, +.rst-versions .rst-current-version:after { display: table; content: "" } + .rst-versions .rst-current-version:after { clear: both } + .rst-versions .rst-current-version .fa { color: #fcfcfc } + .rst-versions .rst-current-version .fa-book { float: left; line-height: 30px } + .rst-versions .rst-current-version .icon-book { float: left } + .rst-versions .rst-current-version.rst-out-of-date { background-color: #E74C3C; color: #fff } + .rst-versions .rst-current-version.rst-active-old-version { background-color: #F1C40F; color: #000 } + .rst-versions.rst-badge { border: none; bottom: 20px; @@ -542,25 +1035,36 @@ ul.fas li .fa-large:before, ul.fas li .fa-large:before { left: auto; max-height: 600px; max-width: 300px; - overflow: scroll; + overflow: hidden; right: 20px; width: auto; } + +.rst-versions.rst-badge.shift-up { + overflow-y: scroll; + height: calc(100vh - 100px); +} + .rst-versions.rst-badge .icon-book { float: none } + .rst-versions.rst-badge .fa-book { float: none } + .rst-versions.rst-badge.shift-up .rst-current-version { text-align: right } + .rst-versions.rst-badge.shift-up .rst-current-version .fa-book { float: left } + .rst-versions.rst-badge.shift-up .rst-current-version .icon-book { float: left } + .rst-versions.rst-badge .rst-current-version { width: auto; height: 30px; @@ -569,17 +1073,21 @@ ul.fas li .fa-large:before, ul.fas li .fa-large:before { display: block; text-align: center } + .rst-other-versions { display: none; padding: 12px; text-align: left; } + .shift-up .rst-other-versions { display: block; } + .rst-other-versions a { border: 0; } + .rst-other-versions hr { display: block; border: 0; @@ -587,19 +1095,24 @@ ul.fas li .fa-large:before, ul.fas li .fa-large:before { padding: 0; border-top: solid 1px #d6edf9 } + .rst-other-versions dl { margin: 0; } + .rst-other-versions dd { display: inline-block; margin: 0 } + .rst-other-versions dd a { display: inline-block; padding: 6px; color: #fcfcfc } -.dev-warning, .outdated-warning { + +.dev-warning, +.outdated-warning { position: absolute; top: 0; width: 100%; @@ -613,121 +1126,218 @@ ul.fas li .fa-large:before, ul.fas li .fa-large:before { background-image: -ms-linear-gradient(-45deg, rgba(0, 0, 0, 0.04) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.04) 50%, rgba(0, 0, 0, 0.04) 75%, transparent 75%, transparent); background-image: linear-gradient(135deg, rgba(0, 0, 0, 0.04) 25%, transparent 25%, transparent 50%, rgba(0, 0, 0, 0.04) 50%, rgba(0, 0, 0, 0.04) 75%, transparent 75%, transparent); font-family: "Roboto", Corbel, Avenir, "Lucida Grande", "Lucida Sans", sans-serif; - font-size: 1.6em; + font-size: 1.1em; text-align: center; background-color: #ffe761; } -@media screen and (max-width: 768px) { - .rst-versions { - width: 85%; - display: none + +/* Large devices (desktops, 992px and up) */ +@media (max-width: 992px) { + .nav-item a.cta { + width: 100%; } - .rst-versions.shift { - display: block + + .powered-by { + padding-bottom: 4rem; } - img { - width: 100%; - height: auto + + .nav-tabs .nav-link { + padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); + font-size: 0.8em; + padding: 1.2em 1.2em; } -} -.button.slack-button > img { - width: inherit; + .nav-tabs .nav-link svg { + margin-right: 5px; + width: 25px; + height: 25px; + } + + section.plugins { + padding-top: 30%; + } + + section.plugins svg { + transform: scale(1.1); + } } -@media (min-width: 768px) { - .col-md-offset-right-2 { - margin-right: 20.666667%; +@media (prefers-color-scheme: dark) { + body { + background-color: var(--darkmode-dark); + color: white; + } + + header, + footer { + background: #0b1f4c !important; } - .quickstart-code { - line-height: 22px; + + .hero-section .cta { + background-color: var(--darkmode-button-light); } - .featurette-heading { - font-size: 5em; + + .hero-section .cta:hover { + background-color: var(--darkmode-button-light-hover); } -} -@media (min-width: 992px) { - .featurette img { - width: 300px; + + .hero-section .explore-cta { + color: white; } - .featurette-heading { - margin-top: 30px; + + .hero-section .explore-cta:hover { + color: white; } -} -@media (max-width: 992px) { - .featurette img { - width: 200px; + + .getting-started::after { + background: linear-gradient(0deg, rgba(52, 52, 87, 0.2) -40.97%, rgba(0, 0, 0, 0) 103.19%), + #0.38.25; + } + + .tab-content { + background: #0.38.25; + } + + .nav-tabs { + background: #0.38.25; + } + + .powered-by_brands { + filter: contrast(2); + } + + div.quickstart-code { + filter: none; + } + + .quickstart-code .shell { + background: #313274; + } + + section.powered-by { + background: var(--darkmode-light); + } + + section.plugins { + background: var(--darkmode-dark); + border-width: 0px; + } + + section.supportus { + background: var(--darkmode-light); + border-width: 0px; + } + + section.pro-cta { + background: var(--darkmode-dark); + border-width: 0px; + } + + .avatar-section:before { + background: var(--avatar-darkmode-before-gradient); + } + + .avatar-section:after { + background: var(--avatar-darkmode-after-gradient); + } + + .blurb { + filter: invert(1); + } + + .slack-channel { + background-color: #110e09; + } + + code { + background-color: #052148; + color: #bbb; + } + + pre { + background-color: #052148; + color: #bbb; + border: 1px solid #052148; + box-shadow: 0 0 0 3px #07387b; + } + + .list-group-item.disabled, + .list-group-item.disabled:focus, + .list-group-item.disabled:hover { + color: var(--bs-gray-100); + } + + a.list-group-item { + background-color: var(--darkmode-dark); + color: var(--bs-gray-500); + } + + a.list-group-item:focus, + a.list-group-item:hover, + button.list-group-item:focus, + button.list-group-item:hover { + color: white; + background-color: var(--darkmode-button-light-hover); + } + + blockquote { + background-color: rgba(0, 82, 204, 0.18); + border-left: 4px solid rgba(108, 169, 255, 0.3); + ; + color: rgb(108, 169, 255); + } + + blockquote.warning { + border-left: 4px solid rgba(239, 136, 84, 0.3); + background-color: rgba(235, 100, 32, 0.18); + color: rgb(239, 136, 84); + } + + blockquote.new-as-of { + border-left: 4px solid rgba(11, 216, 73, 0.3); + background-color: rgba(9, 171, 60, 0.18); + color: rgb(11, 216, 73); + } + + .dev-warning, + .outdated-warning { + background-color: #4e440d; + color: #fff; + } + + .table-striped tbody tr:nth-of-type(odd) { + background-color: #080808 + } + + .table-hover tbody tr:hover { + background-color: #3b3b3b; + color: #bbb; + } + + .table td, + .table th { + border-top: 1px solid #131110; + } + + .table thead th { + border-bottom: 2px solid #131110; + } + + .markdown-body { + color: var(--bs-gray-400); + } + + .markdown-body h1 { + color: white; + } + + .markdown-body h2, + .markdown-body h3, + .markdown-body h4 { + color: var(--bs-gray-300); + } + + .markdown-body a { + color: #ffffff; } -} -@media (prefers-color-scheme: dark) { - body { - background-color: rgb(13, 17, 23); - } - .header { - background-color: #0b1f4c; - } - .blurb { - filter: invert(1); - } - .slack-channel { - background-color: #110e09; - } - - code { - background-color: rgb(22, 27, 34); - color: #bbb; - } - pre { - background-color: rgb(22, 27, 34); - color: #bbb; - border: 1px solid #555; - box-shadow: 0 0 0 3px #111; - } - .highlight-show-language-label { - filter: invert(1); - } - .list-group-item { - background-color: rgb(13, 17, 23); - } - blockquote { - background-color: rgba(0, 82, 204, 0.18); - border-left: 4px solid rgba(108, 169, 255, 0.3);; - color: rgb(108, 169, 255); - } - blockquote.warning { - border-left: 4px solid rgba(239, 136, 84, 0.3); - background-color: rgba(235, 100, 32, 0.18); - color: rgb(239, 136, 84); - } - blockquote.new-as-of { - border-left: 4px solid rgba(11, 216, 73, 0.3); - background-color: rgba(9, 171, 60, 0.18); - color: rgb(11, 216, 73); - } - .dev-warning, .outdated-warning { - background-color: #4e440d; - color: #fff; - } - .table-striped tbody tr:nth-of-type(odd) { - background-color: #080808 - } - .table-hover tbody tr:hover { - background-color: #3b3b3b; - color: #bbb; - } - .table td, .table th { - border-top: 1px solid #131110; - } - .table thead th { - border-bottom: 2px solid #131110; - } - .markdown-body { - color: rgb(184, 181, 171); - } - .markdown-body h1 { - color: #bbb; - } - .markdown-body h2, .markdown-body h3, .markdown-body h4 { - color: #aaa; - } } diff --git a/docs/assets/universe/universe animation.keyshape b/docs/assets/universe/universe animation.keyshape new file mode 100644 index 00000000000..1cc50819ca4 Binary files /dev/null and b/docs/assets/universe/universe animation.keyshape differ diff --git a/docs/assets/universe/universe animation.svg b/docs/assets/universe/universe animation.svg new file mode 100644 index 00000000000..a2056619e59 --- /dev/null +++ b/docs/assets/universe/universe animation.svg @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/assets/versions.json b/docs/assets/versions.json index 16a21eb22b8..9490ee32549 100644 --- a/docs/assets/versions.json +++ b/docs/assets/versions.json @@ -23,6 +23,18 @@ "0.23.9", "0.24.10", "0.25.7", - "0.26.8" + "0.26.8", + "0.27.10", + "0.28.4", + "0.29.4", + "0.30.11", + "0.31.5", + "0.32.4", + "0.33.9", + "0.34.9", + "0.35.20", + "0.36.11", + "0.37.10", + "0.38.25" ] } diff --git a/docs/community/clients.md b/docs/community/clients.md index 914ce0f9c67..3f5d16359b1 100644 --- a/docs/community/clients.md +++ b/docs/community/clients.md @@ -18,16 +18,6 @@ npm install -g dokku-toolbelt See [documentation here](https://www.npmjs.com/package/dokku-toolbelt) for more information. -## (python) dokku-client - -dokku-client is an extensible python-based cli wrapper for remote Dokku hosts. You can install it via the following shell command (assuming you have python and pip installed): - -```shell -pip install dokku-client -``` - -See [documentation here](https://github.com/adamcharnock/dokku-client) for more information. - ## (ruby) Dokku CLI Dokku CLI is a rubygem that acts as a client for your Dokku installation. You can install it via the following shell command (assuming you have ruby and rubygems installed): @@ -38,26 +28,6 @@ gem install dokku-cli See [documentation here](https://github.com/SebastianSzturo/dokku-cli) for more information. -## (ruby) DokkuClient - -DokkuClient is another rubygem that acts as a client for your Dokku installation with built-in support for certain external plugins. You can install it via the following shell command (assuming you have ruby and rubygems installed): - -```shell -gem install dokku_client -``` - -See [documentation here](https://github.com/netguru/dokku_client) for more information. - -## (ruby) Dokkufy - -Dokkufy is a rubygem that handles automation of certain tasks, such as Dokku setup, plugin installation, etc. You can install it via the following shell command (assuming you have ruby and rubygems installed): - -```shell -gem install dokkufy -``` - -See [documentation here](https://github.com/cbetta/dokkufy) for more information. - ## (ruby) Dockland Dockland is a rubygem that acts as a client for your Dokku installation. You can install it via the following shell command (assuming you have ruby and rubygems installed): diff --git a/docs/community/plugins.md b/docs/community/plugins.md index aa3301d05c4..85018d0c573 100644 --- a/docs/community/plugins.md +++ b/docs/community/plugins.md @@ -24,28 +24,39 @@ The following plugins are available and provided by Dokku maintainers. Please f | Plugin | Author | Compatibility | | ------------------------------------------------------------------------------------------------- | --------------------- | --------------------- | -| [CouchDB](https://github.com/dokku/dokku-couchdb) | [dokku][] | 0.4.0+ | -| [Elasticsearch](https://github.com/dokku/dokku-elasticsearch-plugin) | [dokku][] | 0.4.0+ | -| [Grafana/Graphite/Statsd](https://github.com/dokku/dokku-graphite-grafana) | [dokku][] | 0.4.0+ | -| [MariaDB](https://github.com/dokku/dokku-mariadb-plugin) | [dokku][] | 0.4.0+ | -| [Memcached](https://github.com/dokku/dokku-memcached-plugin) | [dokku][] | 0.4.0+ | -| [Mongo](https://github.com/dokku/dokku-mongo-plugin) | [dokku][] | 0.4.0+ | -| [MySQL](https://github.com/dokku/dokku-mysql-plugin) | [dokku][] | 0.4.0+ | -| [Nats](https://github.com/dokku/dokku-nats) | [dokku][] | 0.4.0+ | -| [Postgres](https://github.com/dokku/dokku-postgres-plugin) | [dokku][] | 0.4.0+ | -| [RabbitMQ](https://github.com/dokku/dokku-rabbitmq-plugin) | [dokku][] | 0.4.0+ | -| [Redis](https://github.com/dokku/dokku-redis-plugin) | [dokku][] | 0.4.0+ | -| [RethinkDB](https://github.com/dokku/dokku-rethinkdb-plugin) | [dokku][] | 0.4.0+ | +| [Clickhouse](https://github.com/dokku/dokku-clickhouse) | [dokku][] | 0.4.0+ | | [Copy Files to Image](https://github.com/dokku/dokku-copyfiles-to-image) | [dokku][] | 0.4.0+ | +| [CouchDB](https://github.com/dokku/dokku-couchdb) | [dokku][] | 0.4.0+ | +| [Cron Restart](https://github.com/dokku/dokku-cron-restart) | [dokku][] | 0.30.0+ | +| [Elasticsearch](https://github.com/dokku/dokku-elasticsearch) | [dokku][] | 0.4.0+ | +| [Grafana/Graphite/Statsd](https://github.com/dokku/dokku-graphite) | [dokku][] | 0.4.0+ | | [HTTP Auth](https://github.com/dokku/dokku-http-auth) | [dokku][] | 0.4.0+ | | [Let's Encrypt](https://github.com/dokku/dokku-letsencrypt) | [dokku][] | 0.4.0+ | | [Maintenance mode](https://github.com/dokku/dokku-maintenance) | [dokku][] | 0.4.0+ | +| [MariaDB](https://github.com/dokku/dokku-mariadb) | [dokku][] | 0.4.0+ | +| [Meilisearch](https://github.com/dokku/dokku-meilisearch) | [dokku][] | 0.4.0+ | +| [Memcached](https://github.com/dokku/dokku-memcached) | [dokku][] | 0.4.0+ | +| [Mongo](https://github.com/dokku/dokku-mongo) | [dokku][] | 0.4.0+ | +| [MySQL](https://github.com/dokku/dokku-mysql) | [dokku][] | 0.4.0+ | +| [Nats](https://github.com/dokku/dokku-nats) | [dokku][] | 0.4.0+ | +| [Omnisci](https://github.com/dokku/dokku-omnisci) | [dokku][] | 0.4.0+ | +| [Postgres](https://github.com/dokku/dokku-postgres) | [dokku][] | 0.4.0+ | +| [Pushpin](https://github.com/dokku/dokku-pushpin) | [dokku][] | 0.4.0+ | +| [RabbitMQ](https://github.com/dokku/dokku-rabbitmq) | [dokku][] | 0.4.0+ | | [Redirect](https://github.com/dokku/dokku-redirect) | [dokku][] | 0.4.0+ | -| [Registry](https://github.com/dokku/dokku-registry) | [dokku][] | 0.12.0+ +| [Redis](https://github.com/dokku/dokku-redis) | [dokku][] | 0.4.0+ | +| [Registry](https://github.com/dokku/dokku-registry) | [dokku][] | 0.12.0+ | +| [RethinkDB](https://github.com/dokku/dokku-rethinkdb) | [dokku][] | 0.4.0+ | +| [Scheduler Kubernetes](https://github.com/dokku/dokku-scheduler-kubernetes) | [dokku][] | 0.4.0+ | +| [Scheduler Nomad](https://github.com/dokku/dokku-scheduler-nomad) | [dokku][] | 0.4.0+ | +| [Solr](https://github.com/dokku/dokku-solr) | [dokku][] | 0.4.0+ | +| [SSH Hostkeys](https://github.com/cedricziel/dokku-hostkeys-plugin) | [dokku][] | 0.4.0+ | +| [Typesense](https://github.com/dokku/dokku-typesense) | [dokku][] | 0.4.0+ | ## Community plugins -> Warning: The following plugins have been supplied by our community and may not have been tested by Dokku maintainers. +> [!WARNING] +> The following plugins have been supplied by our community and may not have been tested by Dokku maintainers. ### Datastores @@ -59,6 +70,13 @@ The following plugins are available and provided by Dokku maintainers. Please f | [PostgreSQL](https://github.com/jlachowski/dokku-pg-plugin) | [jlachowski][] | 0.3.x | | [PostgreSQL (single container)](https://github.com/ohardy/dokku-psql) | [ohardy][] | 0.3.x | | [PostgreSQL (single container)](https://github.com/Flink/dokku-psql-single-container) | [flink][] | 0.3.26+ | +| [Edgedb](https://github.com/IgnisDa/dokku-edgedb) | [ignisda][] | 0.27.0+ | + +#### NewSQL + +| Plugin | Author | Compatibility | +| ------------------------------------------------------- | ----------- | ------------- | +| [Surrealdb](https://github.com/IgnisDa/dokku-surrealdb) | [ignisda][] | 0.27.0+ | #### Caching @@ -86,79 +104,57 @@ The following plugins are available and provided by Dokku maintainers. Please f | [InfluxDB](https://github.com/basgys/dokku-influxdb) | [basgys][] | 0.4.x | | [RethinkDB](https://github.com/stuartpb/dokku-rethinkdb-plugin) | [stuartpb][] | 0.3.x | | [Headless Chrome](https://github.com/lazyatom/dokku-chrome) | [lazyatom][] | 0.8.1+ | +| [HTTP OAuth](https://github.com/cheif/dokku-http-oauth) | [cheif][] | 0.4.0+ | ### Plugins Implementing New Dokku Functionality | Plugin | Author | Compatibility | | ------------------------------------------------------------------------------------------------- | --------------------- | --------------------- | | [App name as env](https://github.com/cjblomqvist/dokku-app-name-env) | [cjblomqvist][] | 0.3.x | -| [Docker Direct](https://github.com/josegonzalez/dokku-docker-direct) | [josegonzalez][] | 0.4.0+ | +| [APT](https://github.com/dokku-community/dokku-apt) | [dokku-community][] | 0.18.x+ | +| [Auto Sync](https://github.com/IdeaSynthesis/dokku-autosync)1 | [fomojola][] | 0.8.1+ | +| [Deploy Webhook](https://github.com/IdeaSynthesis/dokku-deploy-webhook)2 | [fomojola][] | 0.8.1+ | +| [DNS](https://github.com/deanmarano/dokku-dns) | [deanmarano][] | 0.31.0+ | +| [Docker auto persist volumes](https://github.com/Flink/dokku-docker-auto-volumes) | [flink][] | 0.4.0+ | +| [Docker Direct](https://github.com/dokku-community/dokku-docker-direct) | [josegonzalez][] | 0.4.0+ | | [Dokku Clone](https://github.com/crisward/dokku-clone) | [crisward][] | 0.4.0+ | -| [Dokku Copy App Config Files](https://github.com/josegonzalez/dokku-supply-config) | [josegonzalez][] | 0.4.0+ | -| [Dockerfile custom path](https://github.com/mimischi/dokku-dockerfile) | [mimischi][] | 0.8.0+ | -| [Dokku Require](https://github.com/crisward/dokku-require)1 | [crisward][] | 0.4.0+ | +| [Dokku Copy App Config Files](https://github.com/dokku-community/dokku-supply-config) | [josegonzalez][] | 0.4.0+ | +| [Dokku Require](https://github.com/crisward/dokku-require)3 | [crisward][] | 0.4.0+ | | [Global Certificates](https://github.com/josegonzalez/dokku-global-cert) | [josegonzalez][] | 0.5.0+ | | [Graduate (Environment Management)](https://github.com/glassechidna/dokku-graduate) | [benjamin-dobell][] | 0.4.0+ | -| [Haproxy tcp load balancer](https://github.com/256dpi/dokku-haproxy) | [256dpi][] | 0.4.0+ | +| [Host post-build command hook](https://github.com/baikunz/dokku-post-deploy-script) | [baikunz][] | 0.4.0+ | +| [Host pre-build command hook](https://github.com/fteychene/dokku-build-hook) | [fteychene][] | 0.4.0+ | | [Hostname](https://github.com/michaelshobbs/dokku-hostname) | [michaelshobbs][] | 0.4.0+ | | [HTTP Auth Secure Apps](https://github.com/matto1990/dokku-secure-apps) | [matto1990][] | 0.4.0+ | +| [Image Size Limit](https://github.com/Tashows/dokku-image-size-limit) | [Tashows][] | | +| [Litestream](https://github.com/AxelTheGerman/dokku-litestream)4 | [AxelTheGerman][] | 0.27.0+ | | [Monit (Health Checks)](https://github.com/mbreit/dokku-monit) | [mbreit][] | 0.8.0+ | -| [Nuke Containers](https://github.com/josegonzalez/dokku-nuke) | [josegonzalez][] | 0.4.0+ | -| [Open App Ports](https://github.com/josegonzalez/dokku-ports) | [josegonzalez][] | 0.3.x | +| [Monit](https://github.com/cjblomqvist/dokku-monit) | [cjblomqvist][] | 0.3.x | +| [Multicast DNS (mDNS)](https://github.com/calyhre/dokku-mdns) | [calyhre][] | 0.33.0+ | +| [Nuke Containers](https://github.com/dokku-community/dokku-nuke) | [josegonzalez][] | 0.4.0+ | | [Proctype Filter](https://github.com/michaelshobbs/dokku-proctype-filter) | [michaelshobbs][] | 0.4.0+ | | [robots.txt](https://notabug.org/candlewaster/dokku-robots.txt) | [candlewaster][] | 0.4.x | -| [SSH Deployment Keys](https://github.com/cedricziel/dokku-deployment-keys)2 | [cedricziel][] | 0.4.0+ | -| [SSH Hostkeys](https://github.com/cedricziel/dokku-hostkeys-plugin)3 | [cedricziel][] | 0.3.x | -| [Application build hook](https://github.com/fteychene/dokku-build-hook) | [fteychene][] | 0.4.0+ | -| [Post Deploy Script](https://github.com/baikunz/dokku-post-deploy-script) | [baikunz][] | 0.4.0+ | -| [Auto Sync](https://github.com/IdeaSynthesis/dokku-autosync)4 | [fomojola][] | 0.8.1+ | -| [Deploy Webhook](https://github.com/IdeaSynthesis/dokku-deploy-webhook)5 | [fomojola][] | 0.8.1+ | - -1 Extends app.json support to include creating volumes and creating / linking databases on push +| [Tor](https://github.com/michaelshobbs/dokku-tor) | [michaelshobbs][] | 0.4.0+ | +| [UFW](https://github.com/dokku-community/dokku-ufw) | [josegonzalez][] | 0.3.x | +| [User Access](https://github.com/mainto/dokku-access) | [mainto][] | 0.4.0+ | +| [User ACL](https://github.com/dokku-community/dokku-acl) | [maciej łebkowski][] | 0.4.0+ | -2 Adds the possibility to add SSH deployment keys to receive private hosted packages +1 Adds the ability to sync an application repo with a remote GitHub repo (useful for automated rebuilds without needing a git push from an external system -3 Adds the ability to add custom hosts to the containers known_hosts file to be able to ssh them easily (useful with deployment keys) +2 Adds the ability to invoke a post-deploy webhook with the IP, port and app name, all with a single config:set). -4 Adds the ability to sync an application repo with a remote GitHub repo (useful for automated rebuilds without needing a git push from an external system +3 Extends app.json support to include creating volumes and creating / linking databases on push -5 Adds the ability to invoke a post-deploy webhook with the IP, port and app name, all with a single config:set). +4 Adds SQLite replication to external object storage via [Litestream](https://litestream.io) ### Other Plugins | Plugin | Author | Compatibility | | ------------------------------------------------------------------------------------------------- | --------------------- | --------------------- | -| [Airbrake deploy](https://github.com/Flink/dokku-airbrake-deploy) | [flink][] | 0.4.0+ | -| [APT](https://github.com/dokku-community/dokku-apt) | [dokku-community][] | 0.18.x+ | -| [Bower install](https://github.com/alexanderbeletsky/dokku-bower-install) | [alexanderbeletsky][] | 0.3.x | -| [Bower/Grunt](https://github.com/thrashr888/dokku-bower-grunt-build-plugin) | [thrashr888][] | 0.3.x | -| [Bower/Gulp](https://github.com/gdi2290/dokku-bower-gulp-build-plugin) | [gdi2290][] | 0.3.x | -| [Bower/Gulp](https://github.com/jagandecapri/dokku-bower-gulp-build-plugin) | [jagandecapri][] | 0.3.x | -| [Builders: bower, compass, gulp, grunt](https://github.com/ignlg/dokku-builders-plugin) | [ignlg][] | 0.4.0+ | | [Chef cookbook](https://github.com/nickcharlton/dokku-cookbook) | [nickcharlton][] | | -| [Docker auto persist volumes](https://github.com/Flink/dokku-docker-auto-volumes) | [flink][] | 0.4.0+ | -| [Hostname](https://github.com/michaelshobbs/dokku-hostname) | [michaelshobbs][] | 0.4.0+ | -| [Limit (Resource management)](https://github.com/sarendsen/dokku-limit) | [sarendsen][] | 0.9.0+ | -| [Logspout](https://github.com/michaelshobbs/dokku-logspout) | [michaelshobbs][] | 0.4.0+ | -| [Syslog](https://github.com/michaelshobbs/dokku-syslog) | [michaelshobbs][] | 0.10.4+ | -| [Long Timeout](https://github.com/investtools/dokku-long-timeout-plugin) | [investtools][] | 0.4.0+ | -| [Monit](https://github.com/cjblomqvist/dokku-monit) | [cjblomqvist][] | 0.3.x | -| [Monorepo](https://github.com/iamale/dokku-monorepo) | [iamale][] | 0.4.0+ | -| [Multi Dockerfile](https://github.com/artofrawr/dokku-multi-dockerfile) | [artofrawr][] | 0.4.0+ | -| [Node](https://github.com/ademuk/dokku-nodejs) | [ademuk][] | 0.3.x | -| [Node](https://github.com/pnegahdar/dokku-node) | [pnegahdar][] | 0.3.x | -| [Rollbar](https://github.com/iloveitaly/dokku-rollbar) | [iloveitaly][] | 0.5.0+ | -| [Slack Notifications](https://github.com/ribot/dokku-slack) | [ribot][] | 0.4.0+ | -| [Telegram Notifications](https://github.com/m0rth1um/dokku-telegram) | [m0rth1um][] | 0.4.0+ | -| [Tor](https://github.com/michaelshobbs/dokku-tor) | [michaelshobbs][] | 0.4.0+ | -| [User ACL](https://github.com/dokku-community/dokku-acl) | [maciej łebkowski][] | 0.4.0+ | -| [Webhooks](https://github.com/nickstenning/dokku-webhooks) | [nickstenning][] | 0.3.x | -| [Wkhtmltopdf](https://github.com/mbriskar/dokku-wkhtmltopdf) | [mbriskar][] | 0.4.0+ | -| [Dokku Wordpress](https://github.com/dokku-community/dokku-wordpress) | [dokku-community][] | 0.4.0+ | -| [Access](https://github.com/mainto/dokku-access) | [mainto][] | 0.4.0+ | -| [Dokku Nginx Trust Proxy](https://github.com/kingsquare/dokku-nginx-vhost-trustproxy) | [kingsquare][] | 0.4.0+ | -| [Fonts](https://github.com/ollej/dokku-fonts) | [ollej][] | 0.19.11+ | | [Discourse](https://github.com/badsyntax/dokku-discourse) | [badsyntax][] | 0.21.4+ | +| [Tailscale](https://github.com/andrew-womeldorf/dokku-tailscale) | [andrew-womeldorf][] | 0.34.4+ | +| [Wordpress](https://github.com/dokku-community/dokku-wordpress) | [dokku-community][] | 0.4.0+ | ### Deprecated Plugins @@ -166,23 +162,41 @@ The following plugins have been removed as their functionality is now in Dokku C | Plugin | Author | In Dokku Since | | ------------------------------------------------------------------------------------------------- | --------------------- | ----------------------------------------- | +| [Airbrake deploy](https://github.com/Flink/dokku-airbrake-deploy) | [flink][] | v0.5.0 (deployment tasks) | | [App User](https://github.com/michaelshobbs/dokku-app-user) | [michaelshobbs][] | v0.7.1 (herokuish 0.3.18) | -| [Custom Domains](https://github.com/neam/dokku-custom-domains) | [neam][] | v0.3.10 (domains plugin) | +| [Bower/Grunt](https://github.com/thrashr888/dokku-bower-grunt-build-plugin) | [thrashr888][] | v0.5.0 (deployment tasks and .buildpacks) | +| [Bower/Gulp](https://github.com/gdi2290/dokku-bower-gulp-build-plugin) | [gdi2290][] | v0.5.0 (deployment tasks and .buildpacks) | +| [Bower install](https://github.com/alexanderbeletsky/dokku-bower-install) | [alexanderbeletsky][] | v0.5.0 (deployment tasks and .buildpacks) | +| [Bower/Gulp](https://github.com/jagandecapri/dokku-bower-gulp-build-plugin) | [jagandecapri][] | v0.5.0 (deployment tasks and .buildpacks) | +| [Builders: bower, compass, gulp, grunt](https://github.com/ignlg/dokku-builders-plugin) | [ignlg][] | v0.5.0 (deployment tasks and .buildpacks) | +| [Custom Domains](https://github.com/neam/dokku-custom-domains) | [neam][] | v0.3.10 (domains plugin) | | [Debug](https://github.com/josegonzalez/dokku-debug) | [josegonzalez][] | v0.3.9 (trace command) | +| [Dockerfile custom path](https://github.com/mimischi/dokku-dockerfile) | [mimischi][] | v0.25.0 (monorepo support) | | [Docker Options](https://github.com/dyson/dokku-docker-options) | [dyson][] | v0.3.17 (docker-options plugin) | | [Dokku Name](https://github.com/alex-sherwin/dokku-name) | [alex-sherwin][] | v0.4.2 (named containers plugin) | | [Events Logger](https://github.com/alessio/dokku-events) | [alessio][] | v0.3.21 (events plugin) | +| [Fonts](https://github.com/ollej/dokku-fonts) | [ollej][] | v0.5.0 (deployment tasks) | | [git rev-parse HEAD in env](https://github.com/dokku-community/dokku-git-rev) | [cjblomqvist][] | v0.12.0 (enhanced core git plugin) | | [Host Port binding](https://github.com/stuartpb/dokku-bind-port) | [stuartpb][] | v0.3.17 (docker-options plugin) | | [Link Containers](https://github.com/rlaneve/dokku-link) | [rlaneve][] | v0.3.17 (docker-options plugin) | | [List Containers](https://github.com/josegonzalez/dokku-list) | [josegonzalez][] | v0.3.14 (ps plugin) | +| [Logspout](https://github.com/michaelshobbs/dokku-logspout) | [michaelshobbs][] | v0.22.6 (vector log shipping) | +| [Long Timeout](https://github.com/investtools/dokku-long-timeout-plugin) | [investtools][] | v0.21.0 (proxy-read-timeout nginx setting)| +| [Monorepo](https://github.com/iamale/dokku-monorepo) | [iamale][] | v0.25.0 (monorepo builder support) | | [Multi-Buildpack](https://github.com/pauldub/dokku-multi-buildpack) | [pauldub][] | v0.4.0 (herokuish) | | [Multiple Domains](https://github.com/wmluke/dokku-domains-plugin)1 | [wmluke][] | v0.3.10 (domains plugin) | +| [Multi Dockerfile](https://github.com/artofrawr/dokku-multi-dockerfile) | [artofrawr][] | v0.25.0 (monorepo support) | | [Named-containers](https://github.com/Flink/dokku-named-containers) | [flink][] | v0.4.2 (named-containers plugin) | +| [Nginx Trust Proxy](https://github.com/kingsquare/dokku-nginx-vhost-trustproxy) | [kingsquare][] | v0.23.0 (nginx x-forwarded-* properties) | +| [Node](https://github.com/ademuk/dokku-nodejs) | [ademuk][] | v0.5.0 (deployment tasks and .buildpacks) | +| [Node](https://github.com/pnegahdar/dokku-node) | [pnegahdar][] | v0.5.0 (deployment tasks and .buildpacks) | | [Nginx-Alt](https://github.com/mikexstudios/dokku-nginx-alt) | [mikexstudios][] | v0.3.10 (domains plugin) | | [Persistent Storage](https://github.com/dyson/dokku-persistent-storage) | [dyson][] | v0.3.17 (docker-options plugin) | | [Pre-Deploy Tasks](https://github.com/michaelshobbs/dokku-app-predeploy-tasks) | [michaelshobbs][] | v0.5.0 (deployment tasks) | | [PrimeCache](https://github.com/darkpixel/dokku-prime-cache) | [darkpixel][] | v0.3.0 (zero downtime deploys) | +| [Rollbar](https://github.com/iloveitaly/dokku-rollbar) | [iloveitaly][] | v0.5.0 (deployment tasks) | +| [Syslog](https://github.com/michaelshobbs/dokku-syslog) | [michaelshobbs][] | v0.22.6 (vector log shipping) | +| [Haproxy tcp load balancer](https://github.com/256dpi/dokku-haproxy) | [256dpi][] | v0.28.0 (haproxt plugin) | | [Process Manager: Circus](https://github.com/apmorton/dokku-circus) | [apmorton][] | v0.3.14/0.7.0 (ps/restart policy plugin) | | [Process Manager: Forego](https://github.com/Flink/dokku-forego) | [flink][] | v0.3.14/0.7.0 (ps plugin) | | [Process Manager: Forego](https://github.com/iskandar/dokku-forego) | [iskandar][] | v0.3.14/0.7.0 (ps plugin) | @@ -191,13 +205,18 @@ The following plugins have been removed as their functionality is now in Dokku C | [Process Manager: Supervisord](https://github.com/statianzo/dokku-supervisord) | [statianzo][] | v0.3.14/0.7.0 (ps plugin) | | [Rebuild application](https://github.com/scottatron/dokku-rebuild) | [scottatron][] | v0.3.14 (ps plugin) | | [Reset mtime](https://github.com/mixxorz/dokku-docker-reset-mtime) | [mixxorz][] | Docker 1.8+ | +| [SSH Deployment Keys](https://github.com/cedricziel/dokku-deployment-keys) | [dokku][] | v0.33.0 (git plugin) | | [Supply env vars to buildpacks](https://github.com/cameron-martin/dokku-build-env)2 | [cameron-martin][] | v0.3.9 (build-env plugin) | +| [Slack Notifications](https://github.com/ribot/dokku-slack) | [ribot][] | v0.5.0 (deployment tasks) | +| [Telegram Notifications](https://github.com/m0rth1um/dokku-telegram) | [m0rth1um][] | v0.5.0 (deployment tasks) | | [user-env-compile](https://github.com/motin/dokku-user-env-compile)2 | [motin][] | v0.3.9 (build-env plugin) | | [user-env-compile](https://github.com/musicglue/dokku-user-env-compile)2 | [musicglue][] | v0.3.9 (build-env plugin) | | [Volume (persistent storage)](https://github.com/ohardy/dokku-volume) | [ohardy][] | v0.5.0 (storage plugin) | +| [Webhooks](https://github.com/nickstenning/dokku-webhooks) | [nickstenning][] | v0.5.0 (deployment tasks) | +| [Wkhtmltopdf](https://github.com/mbriskar/dokku-wkhtmltopdf) | [mbriskar][] | v0.5.0 (deployment tasks) | 1 Conflicts with [VHOSTS Custom Configuration](https://github.com/neam/dokku-nginx-vhosts-custom-configuration) -2 Similar to the heroku-labs feature (see https://devcenter.heroku.com/articles/labs-user-env-compile) +2 Similar to the [heroku-labs feature](https://devcenter.heroku.com/articles/labs-user-env-compile) ### Unmaintained Plugins @@ -221,9 +240,9 @@ The following plugins are no longer maintained by their developers. | [Neo4j](https://github.com/Aomitayo/dokku-neo4j-plugin) | [aomitayo][] | | | [PostGIS](https://github.com/fermuch/dokku-pg-plugin) | [fermuch][] | | | [PostgreSQL (single container)](https://github.com/jeffutter/dokku-postgresql-plugin) | [jeffutter][] | This plugin creates a single postgresql container that all your apps can use. Thus only one instance of postgresql running (good for servers without a ton of memory). | -| [RiakCS (single container)](https://github.com/jeffutter/dokku-riakcs-plugin) | [jeffutter][] | Incompatible with 0.2.0 | | [Redis](https://github.com/luxifer/dokku-redis-plugin) | [luxifer][] | | | [Redis](https://github.com/sekjun9878/dokku-redis-plugin) | [sekjun9878][] | 0.3.26+ | +| [RiakCS (single container)](https://github.com/jeffutter/dokku-riakcs-plugin) | [jeffutter][] | Incompatible with 0.2.0 | 1 Forked from [jezdez/dokku-elasticsearch-plugin](https://github.com/jezdez/dokku-elasticsearch-plugin): uses Elasticsearch 1.2 (instead of 0.90), doesn't depend on dokku-link, runs as elasticsearch user instead of root, and turns off multicast autodiscovery for use in a VPS environment. @@ -235,21 +254,26 @@ The following plugins are no longer maintained by their developers. [alexanderbeletsky]: https://github.com/alexanderbeletsky [alexkruegger]: https://github.com/alexkruegger [aluxian]: https://github.com/aluxian +[andrew-womeldorf]: https://github.com/andrew-womeldorf [aomitayo]: https://github.com/aomitayo [apmorton]: https://github.com/apmorton [artofrawr]: https://github.com/artofrawr +[AxelTheGerman]: https://github.com/AxelTheGerman [badsyntax]: https://github.com/badsyntax [basgys]: https://github.com/basgys [benjamin-dobell]: https://github.com/benjamin-dobell [blag]: https://github.com/blag +[calyhre]: https://github.com/calyhre [cameron-martin]: https://github.com/cameron-martin [candlewaster]: https://notabug.org/candlewaster [cedricziel]: https://github.com/cedricziel [cef]: https://github.com/cef +[cheif]: https://github.com/cheif [cjblomqvist]: https://github.com/cjblomqvist [crisward]: https://github.com/crisward [cu12]: https://github.com/cu12 [darkpixel]: https://github.com/darkpixel +[deanmarano]: https://github.com/deanmarano [dokku]: https://github.com/dokku [dokku-community]: https://github.com/dokku-community [dyson]: https://github.com/dyson @@ -301,11 +325,12 @@ The following plugins are no longer maintained by their developers. [sekjun9878]: https://github.com/sekjun9878 [statianzo]: https://github.com/statianzo [stuartpb]: https://github.com/stuartpb +[Tashows]: https://github.com/Tashows [thrashr888]: https://github.com/thrashr888 [wmluke]: https://github.com/wmluke [zenedith]: https://github.com/zenedith [fteychene]: https://github.com/fteychene -[sarendsen]: https://github.com/sarendsen [baikunz]: https://github.com/baikunz [lazyatom]: https://github.com/lazyatom [ollej]: https://github.com/ollej +[ignisda]: https://github.com/IgnisDa diff --git a/docs/community/tutorials/run-on-external-volume.md b/docs/community/tutorials/run-on-external-volume.md deleted file mode 100644 index 19027a03ad9..00000000000 --- a/docs/community/tutorials/run-on-external-volume.md +++ /dev/null @@ -1,78 +0,0 @@ -# Run Dokku on External Volume - -In order to leverage cloud-provider facilities like _attachable volumes_, (_a.k.a. block storage_) -the following is an easy tutorial to achieve Dokku runs on them. - -> Warning: If the block storage is not available and attached on boot it is possible that containers will not correctly start. Please keep this in mind when considering moving Dokku and/or Docker to network attached storage. - -## Tutorial - -The following is intended to be executed on the dokku host machine as `root`. -Say, _for instance_, that our volume is mapped into the systems as `/dev/vdb1`. - -Stop docker daemon - -```shell -systemctl stop docker -``` - -Prepare the filesystem: - -```shell -mkfs -t ext4 /dev/vdb1 -mkdir /mnt/volume -mount /dev/vdb1 /mnt/volume -``` - -Move the old data directories: - -```shell -mv /home/dokku /home/dokku.OLD -mv /var/lib/docker /var/lib/docker.OLD -mv /var/lib/dokku /var/lib/dokku.OLD -``` - -Move the data on the volume - -```shell -mkdir /mnt/volume/home/ -mkdir -p /mnt/volume/var/lib/ -mv /home/dokku.OLD /mnt/volume/home/dokku -mv /var/lib/dokku.OLD /mnt/volume/var/lib/dokku -mv /var/lib/docker.OLD /mnt/volume/var/lib/docker -``` - -Prepare the mountpoints - -```shell -mkdir /home/dokku -mkdir /var/lib/dokku -mkdir /var/lib/docker -chown dokku:dokku /home/dokku # respect the original ownership -chmod 711 /var/lib/docker # respect the original permissions -``` - -Mount bind - -```shell -mount -o bind /mnt/volume/home/dokku /home/dokku -mount -o bind /mnt/volume/var/lib/dokku /var/lib/dokku -mount -o bind /mnt/volume/var/lib/docker /var/lib/docker -``` - -Start docker daemon - -```shell -systemctl start docker -``` - -At this point all should be working fine, please check it out. - -Then, let the changes be reboot-persistent - -```shell -echo '/dev/vdb1 /mnt/volume ext4 defaults 0 2' | sudo tee -a /etc/fstab -echo '/mnt/volume/home/dokku /home/dokku none defaults,bind 0 0' | sudo tee -a /etc/fstab -echo '/mnt/volume/var/lib/dokku /var/lib/dokku none defaults,bind 0 0' | sudo tee -a /etc/fstab -echo '/mnt/volume/var/lib/docker /var/lib/docker none defaults,bind 0 0' | sudo tee -a /etc/fstab -``` diff --git a/docs/configuration/domains.md b/docs/configuration/domains.md index c3e28f77fa8..c1944279dc1 100644 --- a/docs/configuration/domains.md +++ b/docs/configuration/domains.md @@ -1,5 +1,6 @@ # Domain Configuration +> [!IMPORTANT] > New as of 0.3.10 ``` @@ -12,6 +13,7 @@ domains:enable # Enable VHOST support domains:remove [ ...] # Remove domains from app domains:remove-global [ ...] # Remove global domain names domains:report [|--global] [] # Displays a domains report for one or more apps +domains:reset # Reset app domains to global-configured domains domains:set [ ...] # Set domains for app domains:set-global [ ...] # Set global domain names ``` @@ -32,7 +34,7 @@ If an FQDN such as `dokku.org` is used as the application name, the global virtu You can optionally override this in a plugin by implementing the `nginx-hostname` plugin trigger. If the `nginx-hostname` plugin has no output, the normal hostname algorithm will be executed. See the [plugin trigger documentation](/docs/development/plugin-triggers.md#nginx-hostname) for more information. -## Disabling VHOSTS +## Enabling and disabling VHOSTS If desired, it is possible to disable vhosts with the domains plugin. @@ -40,7 +42,20 @@ If desired, it is possible to disable vhosts with the domains plugin. dokku domains:disable node-js-app ``` -On subsequent deploys, the nginx virtualhost will be discarded. This is useful when deploying internal-facing services that should not be publicly routeable. As of 0.4.0, nginx will still be configured to proxy your app on some random high port. This allows internal services to maintain the same port between deployments. You may change this port by setting `DOKKU_PROXY_PORT` and/or `DOKKU_PROXY_SSL_PORT` (for services configured to use SSL.) +Vhosts can also be disabled for all apps: + +```shell +dokku domains:disable --all +``` + +On subsequent deploys, the nginx virtualhost will be discarded. This is useful when deploying internal-facing services that should not be publicly routeable. As of 0.4.0, nginx will still be configured to proxy your app on some random high port. This allows internal services to maintain the same port between deployments. The non-SSL and SSL ports used can be customized via `dokku proxy:set proxy-port ` and `dokku proxy:set proxy-ssl-port ` - see the [proxy management documentation](/docs/networking/proxy-management.md#setting-proxy-ports) for details. + +To re-enable, run the `domains:enable` subcommand: + +```shell +dokku domains:enable node-js-app +dokku domains:enable --all +``` The domains plugin allows you to specify custom domains for applications. This plugin is aware of any ssl certificates that are imported via `certs:add`. Be aware that disabling domains (with `domains:disable`) will override any custom domains. @@ -61,10 +76,14 @@ dokku domains:remove node-js-app dokku.me # set all custom domains for app dokku domains:set node-js-app dokku.me dokku.org + +# delete all app domains and configure available global domains on it +dokku domains:reset node-js-app ``` ## Displaying domains reports for an app +> [!IMPORTANT] > New as of 0.8.1 You can get a report about the app's domains status using the `domains:report` command: @@ -113,4 +132,4 @@ dokku domains:report node-js-app --domains-app-enabled ## Default site -This is specific to your proxy plugin of choice. See the [nginx documentation](/docs/configuration/nginx.md#default-site) for more information on how to configure this for the default nginx proxy implementation. +This is specific to your proxy plugin of choice. See the [nginx documentation](/docs/networking/proxies/nginx.md#default-site) for more information on how to configure this for the default nginx proxy implementation. diff --git a/docs/configuration/environment-variables.md b/docs/configuration/environment-variables.md index 2914a6d0394..80e9bb65644 100644 --- a/docs/configuration/environment-variables.md +++ b/docs/configuration/environment-variables.md @@ -8,27 +8,30 @@ The `config` plugin provides the following commands to manage your variables: config:show (|--global) Pretty-print an app or global environment config:bundle (|--global) [--merged] Bundle environment into tarfile config:clear (|--global) Clears environment variables -config:export (|--global) [--envfile] Export a global or app environment +config:export (|--global) [--format ] Export a global or app environment config:get (|--global) KEY Display a global or app-specific config value config:keys (|--global) [--merged] Show keys set in environment config:set [--encoded] [--no-restart] (|--global) KEY1=VALUE1 [KEY2=VALUE2 ...] Set one or more config vars config:unset [--no-restart] (|--global) KEY1 [KEY2 ...] Unset one or more config vars ``` -> For security reasons - and as per [docker recommendations](https://github.com/docker/docker/issues/13490) - Dockerfile-based deploys have variables available _only_ during runtime, as noted in [this issue](https://github.com/dokku/dokku/issues/1860). + +> For security reasons - and as per [docker recommendations](https://github.com/docker/docker/issues/13490) - Dockerfile-based deploys have variables available _only_ during runtime, as noted in [this issue](https://github.com/dokku/dokku/issues/1860). Consider using [build arguments](/docs/deployment/builders/dockerfiles.md#build-time-configuration-variables) to expose variables during build-time for Dockerfile apps. Environment variables are available both at run time and during the application build/compilation step for buildpack-based deploys. For buildpack deploys, Dokku will create a `/app/.env` file that can be used for legacy buildpacks. Note that this is _not_ updated when `config:set` or `config:unset` is called, and is only written during a `deploy` or `ps:rebuild`. Developers are encouraged to instead read from the application environment directly, as the proper values will be available then. -> Note: Global `ENV` files are sourced before app-specific `ENV` files. This means that app-specific variables will take precedence over global variables. Configuring your global `ENV` file is manual, and should be considered potentially dangerous as configuration applies to all applications. +> [!NOTE] +> Global environment variables are sourced before app-specific environment variables. This means that app-specific variables will take precedence over global variables. Configuring global environment variables should be considered potentially dangerous as configuration applies to all applications. You can set multiple environment variables at once: ```shell -dokku config:set node-js-app ENV=prod COMPILE_ASSETS=1 +dokku config:set node-js-app APP_ENV=prod COMPILE_ASSETS=1 ``` -> Note: Whitespace and special characters get tricky. If you are using dokku locally you don't need to do any special escaping. If you are using dokku over ssh you will need to backslash-escape spaces: +Whitespace and special characters get tricky. If you are using dokku locally you don't need to do any special escaping. If you are using dokku over ssh you will need to backslash-escape spaces: + ```shell dokku config:set node-js-app KEY="VAL\ WITH\ SPACES" ``` @@ -36,13 +39,26 @@ dokku config:set node-js-app KEY="VAL\ WITH\ SPACES" Dokku can also read base64 encoded values. That's the easiest way to set a value with newlines or spaces. To set a value with newlines you need to base64 encode it first and pass the `--encoded` flag: ```shell -dokku config:set --encoded node-js-app KEY="$(base64 ~/.ssh/id_rsa)" +dokku config:set --encoded node-js-app KEY="$(base64 -w 0 ~/.ssh/id_rsa)" +``` + +Decoded values are stored byte-for-byte exactly as they were encoded. Be aware that `echo "value" | base64` appends a trailing `\n` before encoding because `echo` always adds a newline, and that newline becomes part of the stored value. This is rarely what you want for short inline values - use `printf '%s'` or `echo -n` instead: + +```shell +dokku config:set --encoded node-js-app KEY="$(printf '%s' "myvalue" | base64 -w 0)" +dokku config:set --encoded node-js-app KEY="$(echo -n "myvalue" | base64 -w 0)" +``` + +For multi-line values from a file (such as an SSH private key), `cat` and `base64 -w 0` preserve the file contents exactly: + +```shell +dokku config:set --encoded node-js-app KEY="$(cat ~/.ssh/id_rsa | base64 -w 0)" ``` When setting or unsetting environment variables, you may wish to avoid an application restart. This is useful when developing plugins or when setting multiple environment variables in a scripted manner. To do so, use the `--no-restart` flag: ```shell -dokku config:set --no-restart node-js-app ENV=prod +dokku config:set --no-restart node-js-app APP_ENV=prod ``` If you wish to have the variables output in an `eval`-compatible form, you can use the `config:export` command @@ -51,29 +67,58 @@ If you wish to have the variables output in an `eval`-compatible form, you can u dokku config:export node-js-app # outputs variables in the form: # -# export ENV='prod' +# export APP_ENV='prod' # export COMPILE_ASSETS='1' # source in all the node-js-app app environment variables eval $(dokku config:export node-js-app) ``` -You can control the format of the exported variables with the `--format` flag. -`--format=shell` will output the variables in a single-line for usage in command-line utilities: +You can control the format of the exported variables with the `--format` flag. `--format=shell` will output the variables in a single-line for usage in command-line utilities: ```shell dokku config:export --format shell node-js-app # outputs variables in the form: # -# ENV='prod' COMPILE_ASSETS='1' +# APP_ENV='prod' COMPILE_ASSETS='1' ``` +## Setting Environment Variables via app.json + +Environment variables can also be declared in an `app.json` file in your repository root. This is useful for setting default values, generating secrets, or requiring certain variables to be set before deployment. + +```json +{ + "env": { + "SIMPLE_VAR": "default_value", + "SECRET_KEY": { + "description": "A secret key for signing tokens", + "generator": "secret" + }, + "DATABASE_URL": { + "description": "PostgreSQL connection string", + "required": true + } + } +} +``` + +Environment variables from `app.json` are processed during the first deploy, before the predeploy script runs. Variables can be configured as: + +- **Simple string values**: Set as defaults if not already configured +- **Generated secrets**: Use `"generator": "secret"` to auto-generate a 64-character hex string +- **Required variables**: Use `"required": true` to prompt for or require a value +- **Synced variables**: Use `"sync": true` to update the value on every deploy + +For full details on the `env` schema and behavior, see the [app.json documentation](/docs/appendices/file-formats/app-json.md#env). + ## Special Config Variables The following config variables have special meanings and can be set in a variety of ways. Unless specified via global app config, the values may not be passed into applications. Usage of these values within applications should be considered unsafe, as they are an internal configuration values that may be moved to the internal properties system in the future. -> Warning: This list is not exhaustive, and may vary from version to version. +> [!WARNING] +> This list is not exhaustive, and may vary from version to version. | Name | Default | How to modify | Description | | ------------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | @@ -92,37 +137,18 @@ The following config variables have special meanings and can be set in a variety | `DOKKU_APP_NAME` | none | `--app APP` flag | Name of application to work on. Respected by core plugins. | | `DOKKU_APPS_FORCE_DELETE` | none | `--force` flag | Whether to force delete an application. Also used by other plugins for destructive actions. | | `DOKKU_CHECKS_URL` | `https://dokku.com/docs/deployment/zero-downtime-deploys/` | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | Url displayed during deployment when no CHECKS file exists. | -| `DOKKU_DETACH_CONTAINER` | none | `--detach` flag | Deprecated: Whether to detach a container started via `dokku run`. | | `DOKKU_QUIET_OUTPUT` | none | `--quiet` flag | Silences certain header output for `dokku` commands. | | `DOKKU_RM_CONTAINER` | none | `dokku config:set`
| Deprecated: Whether to keep `dokku run` containers around or not. | | `DOKKU_TRACE` | none | `dokku trace:on`
`dokku trace:off`
`--trace` flag | Turn on very verbose debugging. | -| `DOKKU_APP_PROXY_TYPE` | `nginx` | `dokku proxy:set` | | -| `DOKKU_APP_RESTORE` | `1` | `dokku config:set`
`dokku ps:stop` | | -| `DOKKU_APP_SHELL` | `/bin/bash` | `dokku config:set` | Allows users to change the default shell used by Dokku for `dokku enter` and execution of deployment tasks. | -| `DOKKU_APP_TYPE` | `herokuish` | Auto-detected by using buildpacks or dockerfile | | -| `DOKKU_CHECKS_DISABLED` | none | `dokku checks:disable` | | -| `DOKKU_CHECKS_ENABLED` | none | `dokku checks:enable` | | -| `DOKKU_CHECKS_SKIPPED` | none | `dokku checks:skip` | | -| `DOKKU_CHECKS_WAIT` | `5` | `dokku config:set` | Wait this many seconds for the container to start before running checks. -| `DOKKU_CHECKS_TIMEOUT` | `30` | `dokku config:set` | Wait this many seconds for each response before marking it as a failure. -| `DOKKU_CHECKS_ATTEMPTS` | `5` | `dokku config:set` | Number of retries for to run for a specific check before marking it as a failure -| `DOKKU_DEFAULT_CHECKS_WAIT` | `10` | `dokku config:set` | If no user-defined checks are specified - or if the process being checked is not a `web` process - this is the period of time Dokku will wait before checking that a container is still running. | -| `DOKKU_DISABLE_PROXY` | none | `dokku proxy:disable`
`dokku proxy:enable` | Disables the proxy in front of your application, resulting in publicly routing the docker container. | -| `DOKKU_DISABLE_ANSI_PREFIX_REMOVAL` | none | `dokku config:set`
`/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | Disables removal of the ANSI prefix during deploys. Can be used in cases where the client deployer does not understand ansi escape codes. | -| `DOKKU_DISABLE_APP_AUTOCREATION` | none | `dokku config:set` | Disables automatic creation of a non-existent app on deploy. | -| `DOKKU_DOCKER_STOP_TIMEOUT` | `10` | `dokku config:set` | Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands _also_ respect this value. If not specified, the docker defaults for the [docker stop command](https://docs.docker.com/engine/reference/commandline/stop/) will be used.| -| `DOKKU_DOCKERFILE_CACHE_BUILD` | none | `dokku config:set` | | -| `DOKKU_DOCKERFILE_PORTS` | dockerfile ports | `dokku config:set` | | -| `DOKKU_DOCKERFILE_START_CMD` | none | `dokku config:set` | | -| `DOKKU_PARALLEL_ARGUMENTS`. | none | `dokku config:set` | Allows passing custom arguments to parallel for `ps:*all` commands | -| `DOKKU_PROXY_PORT` | automatically assigned | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*`
`dokku config:set` | | -| `DOKKU_PROXY_SSL_PORT` | automatically assigned | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*`
`dokku config:set` | | -| `DOKKU_PROXY_PORT_MAP` | automatically assigned | `dokku proxy:ports-add`
`dokku proxy:ports-remove`, `dokku proxy:ports-clear` | | -| `DOKKU_SKIP_ALL_CHECKS` | none | `dokku config:set` | | -| `DOKKU_SKIP_CLEANUP` | | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | When a deploy is triggered, if this is set to a non-empty value, then old docker containers and images will not be removed. | -| `DOKKU_SKIP_DEFAULT_CHECKS` | | `dokku config:set` | | -| `DOKKU_SKIP_DEPLOY` | | `dokku config:set` | | -| `DOKKU_START_CMD` | none | `dokku config:set` | Command to run instead of `/start $PROC_TYPE` | | `DOKKU_SYSTEM_GROUP` | `dokku` | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | System group to chown files as. | | `DOKKU_SYSTEM_USER` | `dokku` | `/etc/environment`
`~dokku/.dokkurc`
`~dokku/.dokkurc/*` | System user to chown files as. | -| `DOKKU_WAIT_TO_RETIRE` | `60` | `dokku config:set` | After a successful deploy, the grace period given to old containers before they are stopped/terminated. This is useful for ensuring completion of long-running http connections. | + +## Properties + +### Internal properties + +The following property is recorded internally by the config plugin and is not exposed via `config:report`: + +| Property | Description | Source | +|---|---|---| +| `env-migrated` | Migration sentinel that records the per-app or global `DOKKU_*` env-var → property migration completed for 0.38.0 | `plugins/config/triggers.go` writes `"true"` after the upgrade-time pass | diff --git a/docs/configuration/nginx.md b/docs/configuration/nginx.md deleted file mode 100644 index f4c72494e53..00000000000 --- a/docs/configuration/nginx.md +++ /dev/null @@ -1,417 +0,0 @@ -# Nginx Configuration - -Dokku uses nginx as its server for routing requests to specific applications. By default, access and error logs are written for each app to `/var/log/nginx/${APP}-access.log` and `/var/log/nginx/${APP}-error.log` respectively - -``` -nginx:access-logs [-t] # Show the nginx access logs for an application (-t follows) -nginx:error-logs [-t] # Show the nginx error logs for an application (-t follows) -nginx:report [] [] # Displays a nginx report for one or more apps -nginx:set () # Set or clear an nginx property for an app -nginx:show-config # Display app nginx config -nginx:validate-config [] [--clean] # Validates and optionally cleans up invalid nginx configurations -``` - -## Usage - -### Request Proxying - -By default, the `web` process is the only process proxied by the nginx proxy implementation. Proxying to other process types may be handled by a custom `nginx.conf.sigil` file, as generally described [below](/docs/configuration/nginx.md#customizing-the-nginx-configuration) - -Nginx will proxy the requests in a [round-robin balancing fashion](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream) to the different deployed (scaled) containers running the `web` proctype. This way, the host's resources can be fully leveraged for single-threaded applications (e.g. `dokku ps:scale node-js-app web=4` on a 4-core machine) - -### Binding to specific addresses - -> New as of 0.19.2 - -By default, nginx will listen to all interfaces (`[::]` for IPv6, `0.0.0.0` for IPv4) when proxying requests to applications. This may be changed using the `bind-address-ipv4` and `bind-address-ipv6` properties. This is useful in cases where the proxying should be internal to a network or if there are multiple network interfaces that should respond with different content. - -```shell -dokku nginx:set node-js-app bind-address-ipv4 127.0.0.1 -dokku nginx:set node-js-app bind-address-ipv6 ::1 -``` - -This may be reverted by setting an empty bind address. - -```shell -dokku nginx:set node-js-app bind-address-ipv4 -dokku nginx:set node-js-app bind-address-ipv6 -``` - -> Warning: Validation is not performed on either value. - -Users with apps that contain a custom `nginx.conf.sigil` file will need to modify the files to respect the new `NGINX_BIND_ADDRESS_IPV4` and `NGINX_BIND_ADDRESS_IPV6` variables. - -### HSTS Header - -> New as of 0.20.0 - -If SSL certificates are present, HSTS will be automatically enabled. It can be toggled via `nginx:set`: - -```shell -dokku nginx:set node-js-app hsts true -dokku nginx:set node-js-app hsts false -``` - -The following options are also available via the `nginx:set` command: - -- `hsts` (type: boolean, default: `true`): Enables or disables HSTS for your application. -- `hsts-include-subdomains` (type: boolean, default: `true`): Tells the browser that the HSTS policy also applies to all subdomains of the current domain. -- `hsts-max-age` (type: integer, default: `15724800`): Time in seconds to cache HSTS configuration. -- `hsts-preload` (type: boolean, default: `false`): Tells most major web browsers to include the domain in their HSTS preload lists. - -Beware that if you enable the header and a subsequent deploy of your application results in an HTTP deploy (for whatever reason), the way the header works means that a browser will not attempt to request the HTTP version of your site if the HTTPS version fails until the max-age is reached. - -#### Globally disabling the HSTS Header - -HSTS Header can be disabled for all apps by setting the `hsts` property to false after passing the `--global` flag to `nginx:set`. Changing this value globally or on a per-app basis will require rebuilding the nginx config via the `proxy:build-config` command. - -```shell -dokku nginx:set --global hsts false -``` - -Once the HSTS setting is disabled globally, it can be re-enabled on a per-app basis by setting the `hsts` property as normal. - -```shell -dokku nginx:set node-js-app hsts true -``` - -### Checking access logs - -You may check nginx access logs via the `nginx:access-logs` command. This assumes that app access logs are being stored in `/var/log/nginx/$APP-access.log`, as is the default in the generated `nginx.conf`. - -```shell -dokku nginx:access-logs node-js-app -``` - -You may also follow the logs by specifying the `-t` flag. - -```shell -dokku nginx:access-logs node-js-app -t -``` - -### Checking error logs - -You may check nginx error logs via the `nginx:error-logs` command. This assumes that app error logs are being stored in `/var/log/nginx/$APP-error.log`, as is the default in the generated `nginx.conf`. - -```shell -dokku nginx:error-logs node-js-app -``` - -You may also follow the logs by specifying the `-t` flag. - -```shell -dokku nginx:error-logs node-js-app -t -``` - -### Changing log path - -> New as of 0.20.1 - -The path to where log files are stored can be changed by calling the `nginx:set` command with the following options: - -- `access-log-path` (type: string, default: `${NGINX_LOG_ROOT}/${APP}-access.log`): Log path for nginx access logs -- `error-log-path` (type: string, default: `${NGINX_LOG_ROOT}/${APP}-error.log`): Log path for nginx error logs - -The defaults should not be changed without verifying that the paths will be writeable by nginx. However, this setting is useful for enabling or disabling logging by setting the values to `off`. - -```shell -dokku nginx:set node-js-app access-log-path off -dokku nginx:set node-js-app error-log-path off -``` - -The default value may be set by passing an empty value for the option: - -```shell -dokku nginx:set node-js-app access-log-path -dokku nginx:set node-js-app error-log-path -``` - -In all cases, the nginx config must be regenerated after setting the above values. - -### Changing log format - -> New as of 0.22.0 - -The format of the access log can be changed by calling the `nginx:set` command as follows: - -```shell -dokku nginx:set node-js-app access-log-format custom-format -``` - -Prior to changing the log-format, log formats should be specified at a file such as `/etc/nginx/conf.d/00-log-formats.conf`. This will ensure they are available within your app's nginx context. For instance, the following may be added to the above file. It only needs to be specified once to be used for all apps. - -```nginx -# /etc/nginx/conf.d/00-log-formats.conf -# escape=json was added in nginx 1.11.8 -log_format json_combined escape=json - '{' - '"time_local":"$time_local",' - '"remote_addr":"$remote_addr",' - '"remote_user":"$remote_user",' - '"request":"$request",' - '"status":"$status",' - '"body_bytes_sent":"$body_bytes_sent",' - '"request_time":"$request_time",' - '"http_referrer":"$http_referer",' - '"http_user_agent":"$http_user_agent"' - '}'; -``` - -Next, the format should be set for the given app. - -```shell -dokku nginx:set node-js-app access-log-format json_combined -``` - -Finally, a proxy rebuild will change the format as desired. - -```shell -dokku proxy:build-config node-js-app -``` - -### Specifying a read timeout - -> New as of 0.21.0 - -When proxying requests to your applications, it may be useful to specify a proxy read timeout. This can be done via the `nginx:set` command as follows: - -```shell -dokku nginx:set node-js-app proxy-read-timeout 120s -``` - -The default value is `60s`, and all numeric values _must_ have a trailing time value specified (`s` for seconds, `m` for minutes). - -The default value may be set by passing an empty value for the option: - -```shell -dokku nginx:set node-js-app proxy-read-timeout -``` - -In all cases, the nginx config must be regenerated after setting the above value. - -### Specifying a custom client_max_body_size - -> New as of 0.23.0 - -Users can override the default `client_max_body_size` value - which limits file uploads - via `nginx:set`. Changing this value will only apply to every `server` stanza of the default `nginx.conf.sigil`; users of custom `nginx.conf.sigil` files must update their templates to support the new value. - -```shell -dokku nginx:set node-js-app client-max-body-size 50m -``` - -The default value is empty string, which will result in nginx falling back to any configured, higher-level defaults (or `1m` if unconfigued; all numerical values _must_ have a size unit specified (`k` for kilobytes, `m` for megabytes). - -The default value may be set by passing an empty value for the option: - -```shell -dokku nginx:set node-js-app client-max-body-size -``` - -In all cases, the nginx config must be regenerated after setting the above value. - -Changing this value when using the PHP buildpack (or any other buildpack that uses an intermediary server) will require changing the value in the server config shipped with that buildpack. Consult your buildpack documentation for further details. - -### Showing the nginx config - -For debugging purposes, it may be useful to show the nginx config. This can be achieved via the `nginx:show-config` command. - -```shell -dokku nginx:show-config node-js-app -``` - -### Validating nginx configs - -It may be desired to validate an nginx config outside of the deployment process. To do so, run the `nginx:validate-config` command. With no arguments, this will validate all app nginx configs, one at a time. A minimal wrapper nginx config is generated for each app's nginx config, upon which `nginx -t` will be run. - -```shell -dokku nginx:validate-config -``` - -As app nginx configs are actually executed within a shared context, it is possible for an individual config to be invalid when being validated standalone but _also_ be valid within the global server context. As such, the exit code for the `nginx:validate-config` command is the exit code of `nginx -t` against the server's real nginx config. - -The `nginx:validate-config` command also takes an optional `--clean` flag. If specified, invalid nginx configs will be removed. - -> Warning: Invalid app nginx config's will be removed _even if_ the config is valid in the global server context. - -```shell -dokku nginx:validate-config --clean -``` - -The `--clean` flag may also be specified for a given app: - -```shell -dokku nginx:validate-config node-js-app --clean -``` - -### Customizing the nginx configuration - -> New as of 0.5.0 - -Dokku uses a templating library by the name of [sigil](https://github.com/gliderlabs/sigil) to generate nginx configuration for each app. You may also provide a custom template for your application as follows: - -- Copy the following example template to a file named `nginx.conf.sigil` and either: - - If using a buildpack application, you __must__ check it into the root of your app repo. - - If using a dockerfile or docker image for deploys, either: - - If `WORKDIR` is specified, add the file to the `WORKDIR` specified in the last Dockerfile stage (example: `WORKDIR /app` and `ADD nginx.conf.sigil /app`). - - If no `WORKDIR` is specified, add the file to the root (`/`) of the docker image (example: `ADD nginx.conf.sigil /`). - -> When using a custom `nginx.conf.sigil` file, depending upon your application configuration, you _may_ be exposing the file externally. As this file is extracted before the container is run, you can, safely delete it in a custom `entrypoint.sh` configured in a Dockerfile `ENTRYPOINT`. - -> The default template is available [here](https://github.com/dokku/dokku/blob/master/plugins/nginx-vhosts/templates/nginx.conf.sigil), and can be used as a guide for your own, custom `nginx.conf.sigil` file. Please refer to the appropriate template file version for your Dokku version. - -While enabled by default, using a custom nginx config can be disabled via `nginx:set`. This may be useful in cases where you do not want to allow users to override any higher-level customization of app nginx config. - -```shell -# enable fetching custom config (default) -dokku nginx:set node-js-app disable-custom-config false - -# disable fetching custom config -dokku nginx:set node-js-app disable-custom-config true -``` - -Unsetting this value is the same as enabling custom nginx config usage. - -#### Available template variables - -``` -{{ .APP }} Application name -{{ .APP_SSL_PATH }} Path to SSL certificate and key -{{ .DOKKU_ROOT }} Global Dokku root directory (ex: app dir would be `{{ .DOKKU_ROOT }}/{{ .APP }}`) -{{ .PROXY_PORT }} Non-SSL nginx listener port (same as `DOKKU_PROXY_PORT` config var) -{{ .PROXY_SSL_PORT }} SSL nginx listener port (same as `DOKKU_PROXY_SSL_PORT` config var) -{{ .NOSSL_SERVER_NAME }} List of non-SSL VHOSTS -{{ .PROXY_PORT_MAP }} List of port mappings (same as `DOKKU_PROXY_PORT_MAP` config var) -{{ .PROXY_UPSTREAM_PORTS }} List of configured upstream ports (derived from `DOKKU_PROXY_PORT_MAP` config var) -{{ .RAW_TCP_PORTS }} List of exposed tcp ports as defined by Dockerfile `EXPOSE` directive (**Dockerfile apps only**) -{{ .SSL_INUSE }} Boolean set when an app is SSL-enabled -{{ .SSL_SERVER_NAME }} List of SSL VHOSTS -``` - -Finally, each process type has it's network listeners - a list of IP:PORT pairs for the respective app containers - exposed via an `.DOKKU_APP_${PROCESS_TYPE}_LISTENERS` variable - the `PROCESS_TYPE` will be upper-cased with hyphens transformed into underscores. Users can use the new variables to expose non-web processes via the nginx proxy. - -> Note: Application environment variables are available for use in custom templates. To do so, use the form of `{{ var "FOO" }}` to access a variable named `FOO`. - -#### Customizing via configuration files included by the default templates - -The default nginx.conf template will include everything from your apps `nginx.conf.d/` subdirectory in the main `server {}` block (see above): - -``` -include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; -``` - -That means you can put additional configuration in separate files. To increase the client request header timeout, the following can be performed: - -```shell -mkdir /home/dokku/node-js-app/nginx.conf.d/ -echo 'client_header_timeout 50s;' > /home/dokku/node-js-app/nginx.conf.d/timeout.conf -chown dokku:dokku /home/dokku/node-js-app/nginx.conf.d/upload.conf -service nginx reload -``` - -The example above uses additional configuration files directly on the Dokku host. Unlike the `nginx.conf.sigil` file, these additional files will not be copied over from your application repo, and thus need to be placed in the `/home/dokku/node-js-app/nginx.conf.d/` directory manually. - -For PHP Buildpack users, you will also need to provide a `Procfile` and an accompanying `nginx.conf` file to customize the nginx config _within_ the container. The following are example contents for your `Procfile` - -``` -web: vendor/bin/heroku-php-nginx -C nginx.conf -i php.ini php/ -``` - -Your `nginx.conf` file - not to be confused with Dokku's `nginx.conf.sigil` - would also need to be configured as shown in this example: - -``` -client_header_timeout 50s; -location / { - index index.php; - try_files $uri $uri/ /index.php$is_args$args; -} -``` - -Please adjust the `Procfile` and `nginx.conf` file as appropriate. - -### Custom Error Pages - -By default, Dokku provides custom error pages for the following three categories of errors: - -- 4xx: For all non-404 errors with a 4xx response code. -- 404: For "404 Not Found" errors. -- 5xx: For all 5xx error responses - -These are provided as an alternative to the generic Nginx error page, are shared for _all_ applications, and their contents are located on disk at `/var/lib/dokku/data/nginx-vhosts/dokku-errors`. To customize them for a specific app, create a custom `nginx.conf.sigil` as described above and change the paths to point elsewhere. - -### Default site - -By default, Dokku will route any received request with an unknown HOST header value to the lexicographically first site in the nginx config stack. If this is not the desired behavior, you may want to add the following configuration to the global nginx configuration. - -Create the file at `/etc/nginx/conf.d/00-default-vhost.conf`: - -```nginx -server { - listen 80 default_server; - listen [::]:80 default_server; - - server_name _; - access_log off; - return 410; -} - -# To handle HTTPS requests, you can uncomment the following section. -# -# Please note that in order to let this work as expected, you need a valid -# SSL certificate for any domains being served. Browsers will show SSL -# errors in all other cases. -# -# Note that the key and certificate files in the below example need to -# be copied into /etc/nginx/ssl/ folder. -# -# server { -# listen 443 ssl; -# listen [::]:443 ssl; -# server_name _; -# ssl_certificate /etc/nginx/ssl/cert.crt; -# ssl_certificate_key /etc/nginx/ssl/cert.key; -# access_log off; -# return 410; -# } -``` - -Make sure to reload nginx after creating this file by running `service nginx reload`. - -This will catch all unknown HOST header values and return a `410 Gone` response. You can replace the `return 410;` with `return 444;` which will cause nginx to not respond to requests that do not match known domains (connection refused). - -The configuration file must be loaded before `/etc/nginx/conf.d/dokku.conf`, so it can not be arranged as a vhost in `/etc/nginx/sites-enabled` that is only processed afterwards. - -Alternatively, you may push an app to your Dokku host with a name like "00-default". As long as it lists first in `ls /home/dokku/*/nginx.conf | head`, it will be used as the default nginx vhost. - -## Other - -### Domains plugin - -See the [domain configuration documentation](/docs/configuration/domains.md) for more information on how to configure domains for your app. - -### Customizing hostnames - -See the [customizing hostnames documentation](/docs/configuration/domains.md#customizing-hostnames) for more information on how to configure domains for your app. - -### Disabling VHOSTS - -See the [disabling vhosts documentation](/docs/configuration/domains.md#disabling-vhosts) for more information on how to disable domain usage for your app. - -### Running behind a load balancer - -See the [load balancer documentation](/docs/configuration/ssl.md#running-behind-a-load-balancer) for more information on how to configure your nginx config for running behind a network load balancer. - -### SSL Configuration - -See the [ssl documentation](/docs/configuration/ssl.md) for more information on how to configure SSL certificates for your application. - -### Disabling Nginx - -See the [proxy documentation](/docs/networking/proxy-management.md) for more information on how to disable nginx as the proxy implementation for your app. - -### Managing Proxy Port mappings - -See the [proxy documentation](/docs/networking/proxy-management.md#proxy-port-mapping) for more information on how to manage ports proxied for your app. - -### Regenerating nginx config - -See the [proxy documentation](/docs/networking/proxy-management.md#regenerating-proxy-config) for more information on how to rebuild the nginx proxy configuration for your app. diff --git a/docs/configuration/ssl.md b/docs/configuration/ssl.md index 5b6bfe149f1..a5f492f2416 100644 --- a/docs/configuration/ssl.md +++ b/docs/configuration/ssl.md @@ -1,5 +1,6 @@ # SSL Configuration +> [!IMPORTANT] > New as of 0.4.0 Dokku supports SSL/TLS certificate inspection and CSR/Self-signed certificate generation via the `certs` plugin. Note that whenever SSL/TLS support is enabled SPDY is also enabled. @@ -33,21 +34,26 @@ tar cvf cert-key.tar server.crt server.key dokku certs:add node-js-app < cert-key.tar ``` -> Note: If your `.crt` file came alongside a `.ca-bundle`, you'll want to concatenate those into a single `.crt` file before adding it to the `.tar`. +> [!NOTE] +> If your `.crt` file came alongside a `.ca-bundle`, you'll want to concatenate those into a single `.crt` file before adding it to the `.tar`. ```shell cat yourdomain_com.crt yourdomain_com.ca-bundle > server.crt ``` +> [!NOTE] +> Archives passed to `certs:add` are validated before extraction to prevent path traversal and symlink escape attacks. Archives containing absolute paths, parent directory traversal entries, or symlinks pointing outside the extraction directory will be rejected. + #### SSL and Multiple Domains When an SSL certificate is associated to an application, the certificate will be associated with _all_ domains currently associated with said application. Your certificate _should_ be associated with all of those domains, otherwise accessing the application will result in SSL errors. If you wish to remove one of the domains from the application, refer to the [domain configuration documentation](/docs/configuration/domains.md). -Note that with the default nginx template, requests will be redirected to the `https` version of the domain. If this is not the desired state of request resolution, you may customize the nginx template in use. For more details, see the [nginx documentation](/docs/configuration/nginx.md). +Note that with the default nginx template, requests will be redirected to the `https` version of the domain. If this is not the desired state of request resolution, you may customize the nginx template in use. For more details, see the [nginx documentation](/docs/networking/proxies/nginx.md). ### Certificate generation -> Note: Using this method will create a self-signed certificate, which is only recommended for development or staging use, not production environments. +> [!NOTE] +> Using this method will create a self-signed certificate, which is only recommended for development or staging use, not production environments. The `certs:generate` command will walk you through the correct `openssl` commands to create a key, csr and a self-signed cert for a given app/domain. We automatically put the self-signed cert in place as well as add the specified domain to the application configuration. @@ -69,6 +75,7 @@ dokku certs:show node-js-app key > server.key ### Displaying certificate reports for an app +> [!IMPORTANT] > New as of 0.8.1 You can get a report about the apps ssl status using the `certs:report` command: @@ -124,45 +131,39 @@ dokku certs:report node-js-app --ssl-enabled ## HSTS Header -The [HSTS header](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) is an HTTP header that can inform browsers that all requests to a given site should be made via HTTPS. Dokku does enables this header by default for HTTPS requests. +The [HSTS header](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) is an HTTP header that can inform browsers that all requests to a given site should be made via HTTPS. Dokku enables this header by default for HTTPS requests. -See the [NGINX HSTS documentation](/docs/configuration/nginx.md#hsts-header) for more information on how the HSTS configuration can be managed for your application. +See the [NGINX HSTS documentation](/docs/networking/proxies/nginx.md#hsts-header) for more information on how the HSTS configuration can be managed for your application. ## HTTP/2 support Certain versions of nginx have bugs that prevent [HTTP/2](https://nginx.org/en/docs/http/ngx_http_v2_module.html) from properly responding to all clients, thus causing applications to be unavailable. For HTTP/2 to be enabled in your applications' nginx configs, you need to have installed nginx 1.11.5 or higher. See [issue 2435](https://github.com/dokku/dokku/issues/2435) for more details. -## Running behind a load balancer +### SSL Port Exposure -Your application has access to the HTTP headers `X-Forwarded-Proto`, `X-Forwarded-Port` and `X-Forwarded-For`. These headers indicate the protocol of the original request (HTTP or HTTPS), the port number, and the IP address of the client making the request, respectively. The default configuration is for Nginx to set these headers. +When your app is served from port `80` then the `/home/dokku/APP/nginx.conf` file will automatically be updated to instruct nginx to respond to ssl on port 443 as a new cert is added. If your app uses a non-standard port (perhaps you have a dockerfile deploy exposing port `99999`) you may need to manually expose an ssl port via `dokku ports:add https:443:99999`. -If your server runs behind an HTTP(S) load balancer, then Nginx will see all requests as coming from the load balancer. If your load balancer sets the `X-Forwarded-` headers, you can tell Nginx to pass these headers from load balancer to your application via `nginx:set`: +If an `https:443:*` port mapping already exists when a certificate is installed or renewed, Dokku will preserve it rather than rewriting it from the `http:80:*` mappings. This allows apps that terminate TLS inside the container (for example, with a configuration like `http:80:80 https:443:443`) to keep their explicit mapping across `dokku certs:add` and unattended renewals. -```shell -dokku nginx:set node-js-app x-forwarded-for-value "\$http_x_forwarded_for" -dokku nginx:set node-js-app x-forwarded-port-value "\$http_x_forwarded_port" -dokku nginx:set node-js-app x-forwarded-proto-value "\$http_x_forwarded_proto" -``` +## Other -Only use this option if: -1. All requests are terminated at the load balancer, and forwarded to Nginx -2. The load balancer is configured to send the `X-Forwarded-` headers (this may be off by default) +### Running behind a proxy (`X-Forwarded-Ssl`, etc.) -If it's possible to make HTTP(S) requests directly to Nginx, bypassing the load balancer, or if the load balancer is not configured to set these headers, then it becomes possible for a client to set these headers to arbitrary values. +See the [running behind another proxy documentation](/docs/networking/proxies/nginx.md#running-behind-another-proxy--configuring-x-forwarded--headers) for more information on how to configure your Nginx config when your server is running behind a proxy (e.g. load balancer, etc.). -The `x-forwarded-ssl` property may also be set for application frameworks that require this value. Note that this is a non-standard version of setting `x-forwarded-proto` to `https`, and should only be done as a last resort. +## Properties -```shell -# force-setting value to `on` -dokku nginx:set node-js-app x-forwarded-ssl on +### Read-only flags -# force-setting value to `off` -dokku nginx:set node-js-app x-forwarded-ssl on - -# removing the value from nginx.conf (default) -dokku nginx:set node-js-app x-forwarded-ssl -``` - -### SSL Port Exposure +The following flags surface in `certs:report` but are not managed by `certs:set` - they are derived from the on-disk certificate: -When your app is served from port `80` then the `/home/dokku/APP/nginx.conf` file will automatically be updated to instruct nginx to respond to ssl on port 443 as a new cert is added. If your app uses a non-standard port (perhaps you have a dockerfile deploy exposing port `99999`) you may need to manually expose an ssl port via `dokku proxy:ports-add https:443:99999`. +| Flag | Description | +|---|---| +| `--ssl-enabled` | `true` when an SSL certificate is installed for the app | +| `--ssl-dir` | Absolute path to the per-app certificate directory | +| `--ssl-hostnames` | Hostnames the certificate covers (CN plus Subject Alternative Names) | +| `--ssl-issuer` | Certificate issuer DN | +| `--ssl-subject` | Certificate subject DN | +| `--ssl-verified` | `true` if the certificate chain verifies against the system CA bundle | +| `--ssl-expires-at` | Certificate `notAfter` timestamp | +| `--ssl-starts-at` | Certificate `notBefore` timestamp | diff --git a/docs/deployment/application-deployment.md b/docs/deployment/application-deployment.md index 3b142716994..9f45c4d401c 100644 --- a/docs/deployment/application-deployment.md +++ b/docs/deployment/application-deployment.md @@ -1,6 +1,7 @@ -# Deploying to Dokku +# Deploying an Application -> Note: This walkthrough uses the hostname `dokku.me` in commands. When deploying to your own server, you should substitute the domain `dokku.me` for the domain name or IP address associated with your server. Users of the Vagrant VM included with Dokku can use `dokku.me` which points to the IP of the VM. +> [!NOTE] +> This walkthrough uses the hostname `dokku.me` in commands. When deploying to your own server, you should substitute the domain `dokku.me` for the domain name or IP address associated with your server. Users of the Vagrant VM included with Dokku can use `dokku.me` which points to the IP of the VM. ## Deploy tutorial @@ -8,7 +9,6 @@ Once you have configured Dokku with at least one user, you can deploy applicatio ```shell # from your local machine -# SSH access to github must be enabled on this host git clone https://github.com/heroku/ruby-getting-started ``` @@ -53,7 +53,8 @@ Dokku supports linking a single service to multiple applications as well as link ### Deploy the app -> Warning: Your app should respect the `PORT` environment variable, otherwise it may not respond to web requests. You can find more information in the [port management documentation](/docs/networking/port-management.md).** +> [!WARNING] +> Your app should respect the `PORT` environment variable, otherwise it may not respond to web requests. You can find more information in the [port management documentation](/docs/networking/port-management.md).** Now you can deploy the `ruby-getting-started` app to your Dokku server. All you have to do is add a remote to name the app. Applications are created on-the-fly on the Dokku server. @@ -62,16 +63,18 @@ Now you can deploy the `ruby-getting-started` app to your Dokku server. All you # the remote username *must* be dokku or pushes will fail cd ruby-getting-started git remote add dokku dokku@dokku.me:ruby-getting-started -git push dokku main:master +git push dokku main ``` -> Note: Some tools may not support the short-upstream syntax referenced above, and you may need to prefix +> [!NOTE] +> Some tools may not support the short-upstream syntax referenced above, and you may need to prefix > the upstream with the scheme `ssh://` like so: `ssh://dokku@dokku.me:ruby-getting-started` > Please see the [Git](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a) documentation for more details. -> Note: Your private key should be registered with `ssh-agent` in your local development environment. If you get a `permission denied` error when pushing, you can register your private key as follows: `ssh-add -k ~/`. +> [!NOTE] +> Your private key should be registered with `ssh-agent` in your local development environment. If you get a `permission denied` error when pushing, you can register your private key as follows: `ssh-add -k ~/`. -After running `git push dokku main:master`, you should have output similar to this in your terminal: +After running `git push dokku main`, you should have output similar to this in your terminal: ``` Counting objects: 231, done. @@ -103,9 +106,9 @@ Once the deploy is complete, the application's web URL will be generated as abov Dokku supports deploying applications in a few ways: - [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) via [Herokuish](https://github.com/gliderlabs/herokuish#buildpacks): See the [herokuish buildpacks documentation](/docs/deployment/builders/herokuish-buildpacks.md) to learn about the different ways to specify a buildpack. - - This is the default method used by Dokku. + - This is the default method used by Dokku. - [Dockerfile](https://docs.docker.com/reference/builder/): See the [dockerfile documentation](/docs/deployment/builders/dockerfiles.md) to learn about the different ways to configure Dockerfile-based deploys. -- [Docker Image](https://docs.docker.com/get-started/overview/#docker-objects): See the [docker image documentation](/docs/deployment/methods/images.md) to learn about how to deploy a Docker Image. +- [Docker Image](https://docs.docker.com/get-started/overview/#docker-objects): See the [docker image documentation](/docs/deployment/methods/git.md#initializing-an-app-repository-from-a-docker-image) to learn about how to deploy a Docker Image. ### Setting up SSL @@ -125,8 +128,8 @@ As an alternative, the Dokku project offers an optional letsencrypt plugin that # plugin installation requires root, hence the user change sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git -# configure the plugin -dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=your-email@your.domain.com +# set global email for letsencrypt +dokku letsencrypt:set --global email your-email@your.domain.com # set a custom domain that you own for your application dokku domains:set ruby-getting-started ruby-getting-started.your.domain.com @@ -140,16 +143,16 @@ dokku letsencrypt:cron-job --add ### Skipping deployment -If you only want to rebuild and tag a container, you can skip the deployment phase by setting `$DOKKU_SKIP_DEPLOY` to `true` by running: +If you only want to rebuild and tag a container, you can skip the deployment phase by setting the `skip-deploy` property to `true`: ``` shell # on the Dokku host -dokku config:set ruby-getting-started DOKKU_SKIP_DEPLOY=true +dokku ps:set ruby-getting-started skip-deploy true ``` ### Redeploying or restarting -If you need to redeploy or restart your app: +If you need to redeploy or restart your app: ```shell # on the Dokku host @@ -169,7 +172,8 @@ ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts ssh -T git@github.com ``` -> Warning: if the buildpack or Dockerfile build process require SSH key access for other reasons, the above may not always apply. +> [!WARNING] +> if the buildpack or Dockerfile build process require SSH key access for other reasons, the above may not always apply. ## Deploying to subdomains @@ -180,7 +184,7 @@ If you do not enter a fully qualified domain name when pushing your app, Dokku d # the remote username *must* be dokku or pushes will fail # the below example assumes your app server domain or IP is dokku.me. Push in the form of: dokku@{serveripordomain}:{dokkuappname} git remote add dokku dokku@dokku.me:ruby-getting-started -git push dokku main:master +git push dokku main ``` ``` @@ -194,7 +198,7 @@ You can also specify the fully qualified name as follows: # from your local machine # the remote username *must* be dokku or pushes will fail git remote add dokku dokku@dokku.me:app.dokku.me -git push dokku main:master +git push dokku main ``` ``` @@ -208,7 +212,7 @@ This is useful when you want to deploy to the root domain: # from your local machine # the remote username *must* be dokku or pushes will fail git remote add dokku dokku@dokku.me:dokku.me -git push dokku main:master +git push dokku main ``` ``` @@ -242,7 +246,7 @@ See the [Dockerfile documentation](/docs/deployment/builders/dockerfiles.md) for ## Image tagging -See the [image tagging documentation](/docs/deployment/methods/images.md) for more information on how Docker images can be tagged and deployed for a given application. +See the [image tagging documentation](/docs/deployment/methods/git.md#initializing-an-app-repository-from-a-docker-image) for more information on how Docker images can be tagged and deployed for a given application. ## Specifying a custom buildpack diff --git a/docs/deployment/application-management.md b/docs/deployment/application-management.md index 92e9cfa448c..31812a570d8 100644 --- a/docs/deployment/application-management.md +++ b/docs/deployment/application-management.md @@ -1,5 +1,6 @@ # Application Management +> [!IMPORTANT] > New as of 0.3.1 ``` @@ -7,11 +8,12 @@ apps:clone # Clones an app apps:create # Create a new app apps:destroy # Permanently destroy an app apps:exists # Checks if an app exists -apps:list # List your apps +apps:list [--format stdout|json] # List your apps apps:lock # Locks an app for deployment apps:locked # Checks if an app is locked for deployment apps:rename # Rename an app apps:report [] [] # Display report about an app +apps:set [--global] () # Set or clear an apps property for an app apps:unlock # Unlocks an app for deployment ``` @@ -19,6 +21,7 @@ apps:unlock # Unlocks an app for deployment ### Listing applications +> [!IMPORTANT] > New as of 0.8.1. Use the `apps` command for older versions. You can easily list all available applications using the `apps:list` command: @@ -44,6 +47,18 @@ node-js-app python-app ``` +You can also retrieve the list of apps as a JSON array by using the `--format json` flag, which is useful for programmatic consumption: + +```shell +dokku apps:list --format json +``` + +```json +["node-js-app","python-app"] +``` + +When no apps exist, the JSON output is an empty array (`[]`). + ### Checking if an application exists For CI/CD pipelines, it may be useful to see if an application exists before creating a "review" application for a specific branch. You can do so via the `apps:exists` command: @@ -76,6 +91,20 @@ Once created, you can configure the application as normal, and deploy the applic - Create and link datastores. - Set environment variables. +### Disabling automatic app creation + +By default, pushing to a git remote for an app that does not yet exist on the Dokku host will cause the app to be created automatically. On shared or production hosts this may be undesirable - operators may prefer that an explicit `apps:create` is required first. The `disable-autocreation` global property controls this behavior: + +```shell +dokku apps:set --global disable-autocreation true +``` + +While set, pushes targeting an app that does not exist will be rejected. The default behavior may be restored by passing an empty value: + +```shell +dokku apps:set --global disable-autocreation +``` + ### Removing a deployed app In some cases, you may need to destroy an application, whether it is because the application is temporary or because it was misconfigured. In these cases, you can use the `apps:destroy` command. Performing any destructive actions in Dokku requires confirmation, and this command will ask for the name of the application being deleted before doing so. @@ -117,6 +146,7 @@ Destroying an application will unlink all linked services and destroy any config ### Renaming a deployed app +> [!IMPORTANT] > New as of 0.4.7 You can rename a deployed app using the `apps:rename` command. Note that the application *must* have been deployed at least once, or the rename will not complete successfully: @@ -150,8 +180,15 @@ By default, Dokku will deploy the renamed app, though you can skip the deploy by dokku apps:rename --skip-deploy node-js-app io-js-app ``` +Remember to also change your git remote on your local machine in order to make `git push dokku main` work again. For this you can use `git remote set-url`. + +```shell +git remote set-url dokku dokku@dokku.me:io-js-app +``` + ### Cloning an existing app +> [!IMPORTANT] > New as of 0.11.5 You can clone an existing app using the `apps:clone` command. Note that the application *must* have been deployed at least once, or cloning will not complete successfully: @@ -171,7 +208,8 @@ This will copy all of your app's contents into a new app directory with the name - SSL certificates will not be copied to the new app. - Port mappings with the scheme `https` and host-port `443` will be skipped. -> Warning: If you have exposed specific ports via `docker-options` plugin, or performed anything that cannot be done against multiple applications, `apps:clone` may result in errors. +> [!WARNING] +> If you have exposed specific ports via `docker-options` plugin, or performed anything that cannot be done against multiple applications, `apps:clone` may result in errors. By default, Dokku will deploy this new app, though you can skip the deploy by using the `--skip-deploy` flag: @@ -187,6 +225,7 @@ dokku apps:clone --ignore-existing node-js-app io-js-app ### Locking app deploys +> [!IMPORTANT] > New as of 0.11.6 If you wish to disable deploying for a period of time, this can be done via deploy locks. Normally, deploy locks exist only for the duration of a deploy so as to avoid deploys from colliding, but a deploy lock can be created by running the `apps:lock` command. @@ -201,11 +240,13 @@ dokku apps:lock node-js-app ### Unlocking app deploys +> [!IMPORTANT] > New as of 0.11.6 In some cases, it may be necessary to remove an existing deploy lock. This can be performed via the `apps:unlock` command. -> Warning: Removing the deploy lock *will not* stop in progress deploys. At this time, in progress deploys will need to be manually terminated by someone with server access. +> [!WARNING] +> Removing the deploy lock *will not* stop in progress deploys. At this time, in progress deploys will need to be manually terminated by someone with server access. ```shell dokku apps:unlock node-js-app @@ -219,6 +260,7 @@ dokku apps:unlock node-js-app ### Checking lock status +> [!IMPORTANT] > New as of 0.13.0 In some cases, you may wish to inspect the state of an app lock. To do so, you can issue an `apps:lock` command. This will exit non-zero if there is no app lock in place. @@ -233,6 +275,7 @@ Deploy lock does not exist ### Displaying reports for an app +> [!IMPORTANT] > New as of 0.8.1 You can get a report about the deployed apps using the `apps:report` command: @@ -281,3 +324,26 @@ You can pass flags which will output only the value of the specific information ```shell dokku apps:report node-js-app --app-dir ``` + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `apps:report`. The JSON keys emitted by `apps:report --format json` are the same names with the leading `--app-` stripped (e.g. `global-disable-autocreation`). Legacy keys with the `app-` prefix (e.g. `app-global-disable-autocreation`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `disable-autocreation` | global only | `false` | `--app-global-disable-autocreation` | When `true`, pushes to a remote for an app that does not yet exist are rejected instead of auto-creating the app | + +### Read-only flags + +The following flags surface in `apps:report` but are not managed by `apps:set`: + +| Flag | Description | +|---|---| +| `--app-created-at` | UNIX timestamp of app creation | +| `--app-deploy-source` | Source kind of the last deploy (`git`, `archive`, `docker-image`, `git-sync`) | +| `--app-deploy-source-metadata` | Free-form metadata for the deploy source (commit sha, image ref, URL) | +| `--app-dir` | Absolute path of the app root on disk | +| `--app-locked` | `true` while a deploy or rebuild holds the app lock | diff --git a/docs/deployment/builders/builder-management.md b/docs/deployment/builders/builder-management.md index e15e239b825..fd2b43c8084 100644 --- a/docs/deployment/builders/builder-management.md +++ b/docs/deployment/builders/builder-management.md @@ -1,5 +1,6 @@ # Builder Management +> [!IMPORTANT] > New as of 0.24.0 ``` @@ -17,6 +18,7 @@ Dokku supports the following built-in builders: - `builder-dockerfile`: Builds apps using a `Dockerfile` via `docker build`. See the [dockerfile builder documentation](/docs/deployment/builders/dockerfiles.md) for more information on how this builder functions. - `builder-herokuish`: Builds apps with Heroku's v2a Buildpack specification via `gliderlabs/herokuish`. See the [herokuish builder documentation](/docs/deployment/builders/herokuish-buildpacks.md) for more information on how this builder functions. +- `builder-lambda`: Builds AWS Lambda functions in an environment simulating AWS Lambda runtimes via [lambda-builder](https://github.com/dokku/lambda-builder). See the [lambda builder documentation](/docs/deployment/builders/lambda.md) for more information on how this builder functions. - `builder-null`: Does nothing during the build phase. See the [null builder documentation](/docs/deployment/builders/null.md) for more information on how this builder functions. - `builder-pack`: Builds apps with Cloud Native Buildpacks via the `pack-cli` tool. See the [cloud native buildpacks builder documentation](/docs/deployment/builders/cloud-native-buildpacks.md) for more information on how this builder functions. @@ -24,7 +26,7 @@ Builders run a detection script against a source code repository, and the first ### Overriding the auto-selected builder -If desired, the builder can be specified via the `builder:set` command by speifying a value for `selected`. The selected builder will always be used. +If desired, the builder can be specified via the `builder:set` command by specifying a value for `selected`. The selected builder will always be used. ```shell dokku builder:set node-js-app selected dockerfile @@ -42,6 +44,9 @@ The `selected` property can also be set globally. The global default is an empty dokku builder:set --global selected herokuish ``` +> [!WARNING] +> Selecting a global builder will result in _all_ applications using that builder unless a manual override is selected. + The default value may be set by passing an empty value for the option. ```shell @@ -50,7 +55,8 @@ dokku builder:set --global selected #### Changing the build directory -> Warning: Please keep in mind that setting a custom build directory will result in loss of any changes to the top-level directory, such as the `git.keep-git-dir` property. +> [!WARNING] +> Please keep in mind that setting a custom build directory will result in loss of any changes to the top-level directory, such as the `git.keep-git-dir` property. When deploying a monorepo, it may be desirable to specify the specific build directory to use for a given app. This can be done via the `builder:set` command. If a value is specified and that directory does not exist within the repository, the build will fail. @@ -76,6 +82,28 @@ The default value may be set by passing an empty value for the option. dokku builder:set --global build-dir ``` +#### Skipping container and image cleanup + +After a successful deploy, Dokku removes old containers and images that are no longer referenced by the app. On hosts where image rebuilds are expensive or where operators want to retain prior images for manual rollback, this cleanup can be disabled per-app or globally via the `skip-cleanup` property: + +```shell +dokku builder:set node-js-app skip-cleanup true +``` + +The property can also be set globally: + +```shell +dokku builder:set --global skip-cleanup true +``` + +The default behavior (cleanup enabled) may be restored by passing an empty value: + +```shell +dokku builder:set node-js-app skip-cleanup +``` + +For backwards compatibility with bootstrap-time configuration, the `DOKKU_SKIP_CLEANUP` environment variable in `/etc/environment` or `~dokku/.dokkurc/*` is still honored when the `skip-cleanup` property is unset. The property is the canonical interface and takes precedence when set. + ### Displaying builder reports for an app You can get a report about the app's builder status using the `builder:report` command: @@ -130,17 +158,38 @@ dokku builder:report node-js-app --builder-selected To create a custom builder, the following triggers must be implemented: - `builder-build`: - - arguments: `BUILDER_TYPE` `APP` `SOURCECODE_WORK_DIR` - - description: Creates a docker image named with the output of `common#get_app_image_name $APP`. + - arguments: `BUILDER_TYPE` `APP` `SOURCECODE_WORK_DIR` + - description: Creates a docker image named with the output of `common#get_app_image_name $APP`. - `builder-detect`: - - arguments: `APP` `SOURCECODE_WORK_DIR` - - description: Outputs the name of the builder (without the `builder-` prefix) to use to build the app. + - arguments: `APP` `SOURCECODE_WORK_DIR` + - description: Outputs the name of the builder (without the `builder-` prefix) to use to build the app. - `builder-release`: - - arguments: `BUILDER_TYPE` `APP` `IMAGE_AG` - - description: A post-build, pre-release trigger that can be used to post-process the image. Usually simply tags and labels the image appropriately. + - arguments: `BUILDER_TYPE` `APP` `IMAGE_AG` + - description: A post-build, pre-release trigger that can be used to post-process the image. Usually simply tags and labels the image appropriately. Custom plugins names _must_ have the prefix `builder-` or builder overriding via `builder:set` may not function as expected. Builders can use any tools available on the system to build the docker image, and may even be used to schedule building off-server. The only current requirement is that the image must exist on the server at the end of the `builder-build` command, though this requirement may be relaxed in a future release. For a simple example of how to implement this trigger, see `builder-pack`, which utilizes a cli tool - `pack-cli` - to generate an OCI image that is compatible with Docker and can be scheduled by the official scheduling plugins. + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `builder:report`. The JSON keys emitted by `builder:report --format json` are the same names with the leading `--builder-` stripped (e.g. `selected`, `global-selected`, `computed-selected`). Legacy keys with the `builder-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `build-dir` | app + global | none | `--builder-build-dir`, `--builder-global-build-dir`, `--builder-computed-build-dir` | Subdirectory within the repository to use as the build context | +| `selected` | app + global | none | `--builder-selected`, `--builder-global-selected`, `--builder-computed-selected` | Builder plugin to use; overrides automatic detection | +| `skip-cleanup` | app + global | `false` | `--builder-skip-cleanup`, `--builder-global-skip-cleanup`, `--builder-computed-skip-cleanup` | When `true`, leaves intermediate build artifacts in place after the build | + +### Read-only flags + +The following flags surface in `builder:report` but are not managed by `builder:set`: + +| Flag | Description | +|---|---| +| `--builder-detected` | Builder auto-selected for the app when `selected` is unset | diff --git a/docs/deployment/builders/buildpack-management.md b/docs/deployment/builders/buildpack-management.md new file mode 100644 index 00000000000..4255960d2be --- /dev/null +++ b/docs/deployment/builders/buildpack-management.md @@ -0,0 +1,225 @@ +# Buildpacks Management + +> [!NOTE] +> Buildpacks commands apply to both herokuish and CNB-based builds + +``` +buildpacks:add [--index 1] # Add new app buildpack while inserting into list of buildpacks if necessary +buildpacks:clear # Clear all buildpacks set on the app +buildpacks:list # List all buildpacks for an app +buildpacks:remove # Remove a buildpack set on the app +buildpacks:report [] [] # Displays a buildpack report for one or more apps +buildpacks:set [--index 1] # Set new app buildpack at a given position defaulting to the first buildpack if no index is specified +buildpacks:set --replace [ ...] # Replace the entire ordered buildpack list with the specified buildpacks +``` + +## Usage + +### Listing Buildpacks in Use + +The `buildpacks:list` command can be used to show buildpacks that have been set for an app. This will omit any auto-detected buildpacks. + +```shell +# running for an app with no buildpacks specified +dokku buildpacks:list node-js-app +``` + +``` +-----> test buildpack urls +``` + +```shell +# running for an app with two buildpacks specified +dokku buildpacks:list node-js-app +``` + +``` +-----> test buildpack urls + https://github.com/heroku/heroku-buildpack-python.git + https://github.com/heroku/heroku-buildpack-nodejs.git +``` + +### Adding custom buildpacks + +> Please check the documentation for your particular buildpack as you may need to include configuration files (such as a Procfile) in your project root. + +To add a custom buildpack, use the `buildpacks:add` command: + +```shell +dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git +``` + +When no buildpacks are currently specified, the specified buildpack will be the only one executed for detection and compilation. + +Multiple buildpacks may be specified by using the `buildpacks:add` command multiple times. + +```shell +dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git +``` + +Buildpacks are executed in order, may be inserted at a specified index via the `--index` flag. This flag is specified starting at a 1-index value. + +```shell +# will add the golang buildpack at the second position, bumping all proceeding ones by 1 position +dokku buildpacks:add --index 2 node-js-app https://github.com/heroku/heroku-buildpack-golang.git +``` + +### Overwriting a buildpack position + +In some cases, it may be necessary to swap out a given buildpack. Rather than needing to re-specify each buildpack, the `buildpacks:set` command can be used to overwrite a buildpack at a given position. + +```shell +dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +``` + +By default, this will overwrite the _first_ buildpack specified. To specify an index, the `--index` flag may be used. This flag is specified starting at a 1-index value, and defaults to `1`. + +```shell +# the following are equivalent commands +dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +dokku buildpacks:set --index 1 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +``` + +If the index specified is larger than the number of buildpacks currently configured, the buildpack will be appended to the end of the list. + +```shell +dokku buildpacks:set --index 99 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +``` + +### Replacing the entire buildpack list + +To replace the complete ordered buildpack list in a single command, use the `--replace` flag. This is useful for tooling that needs to apply a full buildpack list atomically instead of running `buildpacks:clear` followed by multiple `buildpacks:add` calls. + +```shell +dokku buildpacks:set --replace node-js-app https://github.com/heroku/heroku-buildpack-ruby.git https://github.com/heroku/heroku-buildpack-nodejs.git +``` + +The buildpacks are executed in the order they are specified, and the previous list is discarded. If any specified buildpack is invalid, the existing list is left unchanged. + +A single buildpack may also be specified to replace the entire list with just that buildpack: + +```shell +dokku buildpacks:set --replace node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +``` + +> [!NOTE] +> The `--replace` flag cannot be combined with the `--index` flag. To remove all buildpacks, use the `buildpacks:clear` command instead of `--replace` with no buildpacks. + +### Removing a buildpack + +> At least one of a buildpack or index must be specified + +A single buildpack can be removed by name via the `buildpacks:remove` command. + +```shell +dokku buildpacks:remove node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +``` + +Buildpacks can also be removed by index via the `--index` flag. This flag is specified starting at a 1-index value. + +```shell +dokku buildpacks:remove node-js-app --index 1 +``` + +### Clearing all buildpacks + +> This does not affect automatically detected buildpacks, nor does it impact any specified `BUILDPACK_URL` environment variable. + +The `buildpacks:clear` command can be used to clear all configured buildpacks for a specified app. + +```shell +dokku buildpacks:clear node-js-app +``` + +### Using a specific buildpack version + +> Always remember to pin your buildpack versions when using the multi-buildpacks method, or you may find deploys changing your deployed environment. + +By default, builders will pin their vendored buildpacks, resulting in a consistent build result for your application. There may be occasions where the pinned version results in a broken deploy, or does not have a particular feature that is required to build your project. To use a more recent version of a given buildpack, the buildpack may be specified _without_ a Git commit SHA like so: + +```shell +# using the latest nodejs buildpack +dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs +``` + +This will use the latest commit on the `master` branch of the specified buildpack. To pin to a newer version of a buildpack, a sha may also be specified by using the form `REPOSITORY_URL#COMMIT_SHA`, where `COMMIT_SHA` is any tree-ish git object - usually a git tag. + +```shell +# using v87 of the nodejs buildpack +dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs#v87 +``` + +### Specifying buildpacks via app.json + +Buildpacks can also be specified in the `app.json` file at the root of your repository: + +```json +{ + "buildpacks": [ + { + "url": "heroku/python" + } + ] +} +``` + +Buildpacks configured via `buildpacks:add` or `buildpacks:set` commands take precedence over those specified in `app.json`. If no buildpacks are configured via commands and `app.json` contains a `buildpacks` entry, those buildpacks will be used. + +Shorthand buildpack references (e.g., `heroku/python`) are expanded to full GitHub URLs automatically. + +### Displaying buildpack reports for an app + +You can get a report about the app's buildpacks status using the `buildpacks:report` command: + +```shell +dokku buildpacks:report +``` + +``` +=====> node-js-app buildpacks information + Buildpacks computed stack: gliderlabs/herokuish:v0.7.0-22 + Buildpacks global stack: gliderlabs/herokuish:latest-24 + Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git + Buildpacks stack: gliderlabs/herokuish:v0.7.0-20 +=====> python-sample buildpacks information + Buildpacks computed stack: gliderlabs/herokuish:latest-24 + Buildpacks global stack: gliderlabs/herokuish:latest-24 + Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git,https://github.com/heroku/heroku-buildpack-python.git + Buildpacks stack: +=====> ruby-sample buildpacks information + Buildpacks computed stack: gliderlabs/herokuish:latest-24 + Buildpacks global stack: gliderlabs/herokuish:latest-24 + Buildpacks list: + Buildpacks stack: +``` + +You can run the command for a specific app also. + +```shell +dokku buildpacks:report node-js-app +``` + +``` +=====> node-js-app buildpacks information + Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku buildpacks:report node-js-app --buildpacks-list +``` + +## Properties + +### Settable properties + +These properties are managed via `buildpacks:set-property` (the legacy command name for this plugin). + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `buildpacks:report`. The JSON keys emitted by `buildpacks:report --format json` are the same names with the leading `--buildpacks-` stripped (e.g. `stack`, `global-stack`, `computed-stack`). Legacy keys with the `buildpacks-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `stack` | app + global | none | `--buildpacks-stack`, `--buildpacks-global-stack`, `--buildpacks-computed-stack` | Herokuish stack image used to compile the app (e.g. `heroku/heroku:24`) | diff --git a/docs/deployment/builders/cloud-native-buildpacks.md b/docs/deployment/builders/cloud-native-buildpacks.md index 572a6d2c473..7728978aa85 100644 --- a/docs/deployment/builders/cloud-native-buildpacks.md +++ b/docs/deployment/builders/cloud-native-buildpacks.md @@ -1,14 +1,21 @@ -# Cloud Native Buildpacks (Experimental) +# Cloud Native Buildpacks +> [!IMPORTANT] > New as of 0.22.0 +``` +builder-pack:report [] [] # Displays a builder-pack report for one or more apps +builder-pack:set () # Set or clear a builder-pack property for an app +``` + ``` buildpacks:set-property [--global|] # Set or clear a buildpacks property for an app ``` Cloud Native Buildpacks are an evolution over the Buildpacks technology provided by the Herokuish builder. See the [herokuish buildpacks documentation](/docs/deployment/builders/herokuish-buildpacks.md) for more information on how to clear buildpack build cache for an application. -> Warning: This functionality uses the `pack` cli from the [Cloud Native Buildpacks](https://buildpacks.io) project to build apps. As the integration is experimental in Dokku, it is likely to change over time. +> [!NOTE] +> This functionality uses the `pack` cli from the [Cloud Native Buildpacks](https://buildpacks.io) project to build apps. ## Usage @@ -20,28 +27,21 @@ Builds will proceed with the `pack` cli for the app from then on. ### Caveats -As this functionality is highly experimental, there are a number of caveats. Please note that not all issuesare listed below. +While pack is well-supported in Dokku, there are a number of caveats. Please note that not all issues are listed below. - Specifying specific buildpacks is not currently possible. - - A future release will add support for specifying buildpacks via the `buildpacks` plugin. + - A future release will add support for specifying buildpacks via the `buildpacks` plugin. - There is currently no way to specify extra arguments for `pack` cli invocations. - - A future release will add support for injecting extra arguments during the build process. + - A future release will add support for injecting extra arguments during the build process. - The default process type is `web`. - Build cache is stored in Docker volumes instead of on disk. As such, `repo:purge-cache` currently has no effect. - - A future version will add integration with the `repo` plugin. + - A future version will add integration with the `repo` plugin. - `pack` is not currently included with Dokku, nor is it added as a package dependency. - - A future version will include it as a package dependency. + - A future version will include it as a package dependency. ### Detection -This builder will be auto-detected in either the following cases: - -- The `DOKKU_CNB_EXPERIMENTAL` app environment variable is set to `1`. - ```shell - dokku config:set --no-restart node-js-app DOKKU_CNB_EXPERIMENTAL=1 - ``` -- A `project.toml` file exists in the root of the app repository. - - This file is consumed by `pack-cli` and used to describe how the app is built. +This builder will be auto-detected if a `project.toml` file exists in the root of the app repository. This file is consumed by `pack-cli` and used to describe how the app is built. The builder can also be specified via the `builder:set` command: @@ -53,12 +53,19 @@ dokku builder:set node-js-app selected pack ### Changing the `project.toml` location -When deploying a monorepo, it may be desirable to specify the specific path of the `project.toml` file to use for a given app. This can be done via the `builder-pack:set` command. If a value other than `project.toml` is specified and that file does not exist in the app's build directory, Dokku will continue the build process as if the repository has no `project.toml`. +The `project.toml` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `projecttoml-path` property: ```shell -dokku builder-pack:set node-js-app projecttoml-path project2.toml +dokku builder-pack:set node-js-app projecttoml-path .dokku/project.toml ``` +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 `project.toml`. + The default value may be set by passing an empty value for the option: ```shell @@ -79,29 +86,32 @@ dokku builder-pack:set --global projecttoml-path ### Displaying builder-pack reports for an app +> [!IMPORTANT] > New as of 0.25.0 You can get a report about the app's storage status using the `builder-pack:report` command: ```shell -dokku app-json:report +dokku builder-pack:report ``` ``` =====> node-js-app builder-pack information - Builder-pack computed projecttoml path: project2.toml - Builder-pack global projecttoml path: project.toml - Builder-pack projecttoml path: project2.toml + Builder pack computed projecttoml path: project2.toml + Builder pack global projecttoml path: + Builder pack projecttoml path: project2.toml =====> python-sample builder-pack information - Builder-pack computed projecttoml path: project.toml - Builder-pack global projecttoml path: project.toml - Builder-pack projecttoml path: + Builder pack computed projecttoml path: project.toml + Builder pack global projecttoml path: + Builder pack projecttoml path: =====> ruby-sample builder-pack information - Builder-pack computed projecttoml path: project.toml - Builder-pack global projecttoml path: project.json - Builder-pack projecttoml path: + Builder pack computed projecttoml path: project.toml + Builder pack global projecttoml path: + Builder pack projecttoml path: ``` +The `projecttoml-path` and `global-projecttoml-path` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-projecttoml-path` key holds the effective value used at build time, falling back to the global value (where one has been set) and then to the built-in default of `project.toml`. + You can run the command for a specific app also. ```shell @@ -110,9 +120,9 @@ dokku builder-pack:report node-js-app ``` =====> node-js-app builder-pack information - Builder-pack computed projecttoml path: project2.toml - Builder-pack global projecttoml path: project.toml - Builder-pack projecttoml path: project2.toml + Builder pack computed projecttoml path: project2.toml + Builder pack global projecttoml path: + Builder pack projecttoml path: project2.toml ``` You can pass flags which will output only the value of the specific information you want. For example: @@ -127,9 +137,10 @@ project2.toml ### Customizing the Buildpack stack builder +> [!IMPORTANT] > New as of 0.23.0 -The default stack builder in use by CNB buildpacks in Dokku is based on `heroku/buildpacks`. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `buildpacks:set-property` command. +The default stack builder in use by CNB buildpacks in Dokku is based on `heroku/builder:24`. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `buildpacks:set-property` command. ```shell dokku buildpacks:set-property node-js-app stack paketobuildpacks/build:base-cnb @@ -143,7 +154,7 @@ dokku buildpacks:set-property node-js-app stack A change in the stack builder value will execute the `post-stack-set` trigger. -Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_CNB_BUILDER` environment variable (`heroku/buildpacks` by default). +Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_CNB_BUILDER` environment variable (`heroku/builder:24` by default). ```shell # set globally @@ -152,3 +163,15 @@ dokku buildpacks:set-property --global stack paketobuildpacks/build:base-cnb # unset globally dokku buildpacks:set-property --global stack ``` + +### Specifying commands via Procfile + +See the [Procfile documentation](/docs/processes/process-management.md#procfile) for more information on how to specify different processes for your app. + +## Properties + +### Settable properties + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `projecttoml-path` | app + global | `project.toml` | `--builder-pack-projecttoml-path`, `--builder-pack-global-projecttoml-path`, `--builder-pack-computed-projecttoml-path` | Path within the app to the CNB `project.toml` manifest, relative to the build root | diff --git a/docs/deployment/builders/dockerfiles.md b/docs/deployment/builders/dockerfiles.md index 841e79e2a73..ce48be6c1aa 100644 --- a/docs/deployment/builders/dockerfiles.md +++ b/docs/deployment/builders/dockerfiles.md @@ -1,7 +1,13 @@ # Dockerfile Deployment +> [!IMPORTANT] > New as of 0.3.15 +``` +builder-dockerfile:report [] [] # Displays a builder-dockerfile report for one or more apps +builder-dockerfile:set () # Set or clear a builder-dockerfile property for an app +``` + While Dokku normally defaults to using [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) for deployment, you can also use Docker's native `Dockerfile` system to define a container. > Dockerfile support is considered a *power user* feature. By using Dockerfile-based deployment, you agree that you will not have the same comfort as that enjoyed by buildpack users, and Dokku features may work differently. Differences between the two systems will be documented here. @@ -21,22 +27,29 @@ Dokku will only select the `dockerfile` builder if both the `herokuish` and `pac ### Switching from buildpack deployments -If an application was previously deployed via buildpacks, the following commands should be run before a Dockerfile deploy will succeed: +If an application was previously deployed via buildpacks and ports were customized, the following commands should be run prior to a deploy to ensure the Dockerfile ports are respected: ```shell -dokku config:unset --no-restart node-js-app DOKKU_PROXY_PORT_MAP +dokku ports:clear node-js-app ``` ### Changing the `Dockerfile` location > The previous method to perform this - via `docker-options:add` - should be removed in favor of the `builder-dockerfile:set` command outlined here. -When deploying a monorepo, it may be desirable to specify the specific path of the `Dockerfile` file to use for a given app. This can be done via the `builder-dockerfile:set` command. If a value is specified and that file does not exist in the app's build directory, then the build will fail. +The `Dockerfile` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `dockerfile-path` property: ```shell -dokku builder-dockerfile:set node-js-app dockerfile-path Dockerfile2 +dokku builder-dockerfile:set node-js-app dockerfile-path .dokku/Dockerfile ``` +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, the build will fail. + The default value may be set by passing an empty value for the option: ```shell @@ -57,29 +70,32 @@ dokku builder-dockerfile:set --global dockerfile-path ### Displaying builder-dockerfile reports for an app +> [!IMPORTANT] > New as of 0.25.0 You can get a report about the app's storage status using the `builder-dockerfile:report` command: ```shell -dokku app-json:report +dokku builder-dockerfile:report ``` ``` =====> node-js-app builder-dockerfile information - Builder-dockerfile computed dockerfile path: Dockerfile2 - Builder-dockerfile global dockerfile path: Dockerfile - Builder-dockerfile dockerfile path: Dockerfile2 + Builder dockerfile computed dockerfile path: Dockerfile2 + Builder dockerfile global dockerfile path: + Builder dockerfile dockerfile path: Dockerfile2 =====> python-sample builder-dockerfile information - Builder-dockerfile computed dockerfile path: Dockerfile - Builder-dockerfile global dockerfile path: Dockerfile - Builder-dockerfile dockerfile path: + Builder dockerfile computed dockerfile path: Dockerfile + Builder dockerfile global dockerfile path: + Builder dockerfile dockerfile path: =====> ruby-sample builder-dockerfile information - Builder-dockerfile computed dockerfile path: Dockerfile - Builder-dockerfile global dockerfile path: Dockerfile - Builder-dockerfile dockerfile path: + Builder dockerfile computed dockerfile path: Dockerfile + Builder dockerfile global dockerfile path: + Builder dockerfile dockerfile path: ``` +The `dockerfile-path` and `global-dockerfile-path` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-dockerfile-path` key holds the effective value used at deploy time, falling back to the global value (where one has been set) and then to the built-in default of `Dockerfile`. + You can run the command for a specific app also. ```shell @@ -88,9 +104,9 @@ dokku builder-dockerfile:report node-js-app ``` =====> node-js-app builder-dockerfile information - Builder-dockerfile computed dockerfile path: Dockerfile2 - Builder-dockerfile global dockerfile path: Dockerfile - Builder-dockerfile dockerfile path: Dockerfile2 + Builder dockerfile computed dockerfile path: Dockerfile2 + Builder dockerfile global dockerfile path: + Builder dockerfile dockerfile path: Dockerfile2 ``` You can pass flags which will output only the value of the specific information you want. For example: @@ -107,16 +123,16 @@ Dockerfile2 For security reasons - and as per [Docker recommendations](https://github.com/docker/docker/issues/13490) - Dockerfile-based deploys have variables available only during runtime. -For users that require customization in the `build` phase, you may use build arguments via the [docker-options plugin](docs/advanced-usage/docker-options.md): +For users that require customization in the `build` phase, you may use build arguments via the [docker-options plugin](/docs/advanced-usage/docker-options.md). All environment variables set by the `config` plugin are automatically exported during a docker build, and thus `--build-arg` only requires setting a key without a value. ```shell -dokku docker-options:add node-js-app build '--build-arg NODE_ENV=production' +dokku docker-options:add node-js-app build '--build-arg NODE_ENV' ``` Once set, the Dockerfile usage would be as follows: ```Dockerfile -FROM ubuntu:18.04 +FROM ubuntu:24.04 # set the argument default ARG NODE_ENV=production @@ -128,7 +144,7 @@ RUN echo $NODE_ENV You may also set the argument as an environment variable ```Dockerfile -FROM ubuntu:18.04 +FROM ubuntu:24.04 # set the argument default ARG NODE_ENV=production @@ -143,18 +159,18 @@ ENV NODE_ENV ${NODE_ENV} RUN echo $NODE_ENV ``` -### Building images with Docker Buildkit +### Building images with Docker BuildKit -If your Dockerfile is using Docker engine's [buildkit](https://docs.docker.com/develop/develop-images/build_enhancements/) (not to be confused with buildpacks), then the `DOCKER_BUILDKIT=1` environment variable needs to be set. Additionally, complete build log output can be forced via `BUILDKIT_PROGRESS=plain`. Both of these environment variables can be set as follows: +If your Dockerfile is using Docker Engine's [BuildKit](https://docs.docker.com/develop/develop-images/build_enhancements/) (not to be confused with buildpacks), then the `DOCKER_BUILDKIT=1` environment variable needs to be set (unless you're using Docker Engine v24 or higher, which [uses BuildKit by default](https://docs.docker.com/build/buildkit/#getting-started)). Additionally, complete build log output can be forced via `BUILDKIT_PROGRESS=plain`. Both of these environment variables can be set as follows: ```shell echo "export DOCKER_BUILDKIT=1" | sudo tee -a /etc/default/dokku echo "export BUILDKIT_PROGRESS=plain" | sudo tee -a /etc/default/dokku ``` -#### Buildkit directory caching +#### BuildKit directory caching -Buildkit implements the `RUN --mount` option, enabling mount directory caches for `RUN` directives. The following is an example that mounts debian packaging related directories, which can speed up fetching of remote package data. +BuildKit implements the `RUN --mount` option, enabling mount directory caches for `RUN` directives. The following is an example that mounts debian packaging related directories, which can speed up fetching of remote package data. ```Dockerfile FROM debian:latest @@ -171,7 +187,7 @@ You would adjust the cache directory for whatever application cache you have, e. ### Customizing the run command -By default no arguments are passed to `docker run` when deploying the container and the `CMD` or `ENTRYPOINT` defined in the `Dockerfile` are executed. You can take advantage of docker ability of overriding the `CMD` or passing parameters to your `ENTRYPOINT` setting `$DOKKU_DOCKERFILE_START_CMD`. Let's say for example you are deploying a base Node.js image, with the following `ENTRYPOINT`: +By default no arguments are passed to `docker run` when deploying the container and the `CMD` or `ENTRYPOINT` defined in the `Dockerfile` are executed. You can take advantage of docker's ability to override the `CMD` or pass parameters to your `ENTRYPOINT` by setting the `dockerfile-start-cmd` property on the `ps` plugin. Let's say for example you are deploying a base Node.js image, with the following `ENTRYPOINT`: ```Dockerfile ENTRYPOINT ["node"] @@ -180,44 +196,30 @@ ENTRYPOINT ["node"] You can do: ```shell -dokku config:set node-js-app DOKKU_DOCKERFILE_START_CMD="--harmony server.js" +dokku ps:set node-js-app dockerfile-start-cmd "--harmony server.js" ``` -To tell Docker what to run. +To tell Docker what to run. Pass an empty value to clear the override: -Setting `$DOKKU_DOCKERFILE_CACHE_BUILD` to `true` or `false` will enable or disable Docker's image layer cache. Lastly, for more granular build control, you may also pass any `docker build` option to `docker`, by setting `$DOKKU_DOCKER_BUILD_OPTS`. +```shell +dokku ps:set node-js-app dockerfile-start-cmd +``` ### Procfiles and multiple processes +> [!IMPORTANT] > New as of 0.5.0 -You can also customize the run command using a `Procfile`, much like you would on Heroku or -with a buildpack deployed app. The `Procfile` should contain one or more lines defining [process types and associated commands](https://devcenter.heroku.com/articles/procfile#declaring-process-types). -When you deploy your app, a Docker image will be built. The `Procfile` will be extracted from the image -(it must be in the folder defined in your `Dockerfile` as `WORKDIR` or `/app`) and the commands -in it will be passed to `docker run` to start your process(es). Here's an example `Procfile`: +See the [Procfile documentation](/docs/processes/process-management.md#procfile) for more information on how to specify different processes for your app. -```Procfile -web: bin/run-prod.sh -worker: bin/run-worker.sh -``` - -And `Dockerfile`: +### Exposed ports -```Dockerfile -FROM ubuntu:18.04 -WORKDIR /app -COPY . ./ -CMD ["bin/run-dev.sh"] -``` +See the [port management documentation](/docs/networking/port-management.md) for more information on how Dokku exposes ports for applications and how you can configure these for your app. -When you deploy this app the `web` process will automatically be scaled to 1 and your Docker container -will be started basically using the command `docker run bin/run-prod.sh`. If you want to also run -a worker container for this app, you can run `dokku ps:scale worker=1` and a new container will be -started by running `docker run bin/run-worker.sh` (the actual `docker run` commands are a bit more -complex, but this is the basic idea). If you use an `ENTRYPOINT` in your `Dockerfile`, the lines -in your `Procfile` will be passed as arguments to the `ENTRYPOINT` script instead of being executed. +## Properties -### Exposed ports +### Settable properties -See the [port management documentation](/docs/networking/port-management.md) for more information on how Dokku exposes ports for applications and how you can configure these for your app. +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `dockerfile-path` | app + global | `Dockerfile` | `--builder-dockerfile-dockerfile-path`, `--builder-dockerfile-global-dockerfile-path`, `--builder-dockerfile-computed-dockerfile-path` | Path within the app to the Dockerfile used by the dockerfile builder | diff --git a/docs/deployment/builders/herokuish-buildpacks.md b/docs/deployment/builders/herokuish-buildpacks.md index 0f863085f9a..c45aa207bdf 100644 --- a/docs/deployment/builders/herokuish-buildpacks.md +++ b/docs/deployment/builders/herokuish-buildpacks.md @@ -1,25 +1,26 @@ -# Herokuish Buildpack Deployment +# Herokuish Buildpacks +> [!IMPORTANT] > Subcommands new as of 0.15.0 ``` -buildpacks:add [--index 1] # Add new app buildpack while inserting into list of buildpacks if necessary -buildpacks:clear # Clear all buildpacks set on the app -buildpacks:list # List all buildpacks for an app -buildpacks:remove # Remove a buildpack set on the app -buildpacks:report [] [] # Displays a buildpack report for one or more apps -buildpacks:set [--index 1] # Set new app buildpack at a given position defaulting to the first buildpack if no index is specified buildpacks:set-property [--global|] # Set or clear a buildpacks property for an app ``` -> Warning: If using the `buildpacks` plugin, be sure to unset any `BUILDPACK_URL` and remove any such entries from a committed `.env` file. A specified `BUILDPACK_URL` will always override a `.buildpacks` file or the buildpacks plugin. +``` +builder-herokuish:report [] [] # Displays a builder-herokuish report for one or more apps +builder-herokuish:set () # Set or clear a builder-herokuish property for an app +``` + +> [!WARNING] +> If using the `buildpacks` plugin, be sure to unset any `BUILDPACK_URL` and remove any such entries from a committed `.env` file. A specified `BUILDPACK_URL` will always override a `.buildpacks` file or the buildpacks plugin. Dokku normally defaults to using [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) for deployment, though this may be overridden by committing a valid `Dockerfile` to the root of your repository and pushing the repository to your Dokku installation. To avoid this automatic `Dockerfile` deployment detection, you may do one of the following: - Set a `BUILDPACK_URL` environment variable - - This can be done via `dokku config:set` or via a committed `.env` file in the root of the repository. See the [environment variable documentation](/docs/configuration/environment-variables.md) for more details. + - This can be done via `dokku config:set` or via a committed `.env` file in the root of the repository. See the [environment variable documentation](/docs/configuration/environment-variables.md) for more details. - Create a `.buildpacks` file in the root of your repository. - - This can be via a committed `.buildpacks` file or managed via the `buildpacks` plugin commands. + - This can be via a committed `.buildpacks` file or managed via the `buildpacks` plugin commands. This page will cover usage of the `buildpacks` plugin. @@ -30,9 +31,9 @@ This page will cover usage of the `buildpacks` plugin. This builder will be auto-detected in either the following cases: - The `BUILDPACK_URL` app environment variable is set. - - This can be done via `dokku config:set` or via a committed `.env` file in the root of the repository. See the [environment variable documentation](/docs/configuration/environment-variables.md) for more details. + - This can be done via `dokku config:set` or via a committed `.env` file in the root of the repository. See the [environment variable documentation](/docs/configuration/environment-variables.md) for more details. - A `.buildpacks` file exists in the root of the app repository. - - This can be via a committed `.buildpacks` file or managed via the `buildpacks` plugin commands. + - This can be via a committed `.buildpacks` file or managed via the `buildpacks` plugin commands. The builder can also be specified via the `builder:set` command: @@ -42,173 +43,115 @@ dokku builder:set node-js-app selected herokuish > Dokku will only select the `dockerfile` builder if both the `herokuish` and `pack` builders are not detected and a Dockerfile exists. See the [dockerfile builder documentation](/docs/deployment/builders/dockerfiles.md) for more information on how that builder functions. -### Listing Buildpacks in Use - -The `buildpacks:list` command can be used to show buildpacks that have been set for an app. This will omit any auto-detected buildpacks. +### Customizing the Buildpack stack builder -```shell -# running for an app with no buildpacks specified -dokku buildpacks:list node-js-app -``` +> [!IMPORTANT] +> New as of 0.23.0 -``` ------> test buildpack urls -``` +The default stack builder in use by Herokuish buildpacks in Dokku is based on `gliderlabs/herokuish:latest`. Typically, this is installed via an OS package which pulls the requisite Docker image. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `buildpacks:set-property` command. ```shell -# running for an app with two buildpacks specified -dokku buildpacks:list node-js-app -``` - -``` ------> test buildpack urls - https://github.com/heroku/heroku-buildpack-python.git - https://github.com/heroku/heroku-buildpack-nodejs.git +dokku buildpacks:set-property node-js-app stack gliderlabs/herokuish:latest ``` -### Adding custom buildpacks - -> Please check the documentation for your particular buildpack as you may need to include configuration files (such as a Procfile) in your project root. - -To add a custom buildpack, use the `buildpacks:add` command: +The specified stack builder can also be unset by omitting the name of the stack builder when calling `buildpacks:set-property`. ```shell -dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git +dokku buildpacks:set-property node-js-app stack ``` -When no buildpacks are currently specified, the specified buildpack will be the only one executed for detection and compilation. +A change in the stack builder value will execute the `post-stack-set` trigger. -Multiple buildpacks may be specified by using the `buildpacks:add` command multiple times. +Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_IMAGE` environment variable (`gliderlabs/herokuish:latest-24` by default). ```shell -dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-ruby.git -dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git -``` - -Buildpacks are executed in order, may be inserted at a specified index via the `--index` flag. This flag is specified starting at a 1-index value. +# set globally +dokku buildpacks:set-property --global stack gliderlabs/herokuish:latest -```shell -# will add the golang buildpack at the second position, bumping all proceeding ones by 1 position -dokku buildpacks:add --index 2 node-js-app https://github.com/heroku/heroku-buildpack-golang.git +# unset globally +dokku buildpacks:set-property --global stack ``` -### Overwriting a buildpack position +### Allowing herokuish for non-amd64 platforms -In some cases, it may be necessary to swap out a given buildpack. Rather than needing to re-specify each buildpack, the `buildpacks:set` command can be used to overwrite a buildpack at a given position. +> [!IMPORTANT] +> New as of 0.29.0 -```shell -dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git -``` +By default, the builder-herokuish plugin is not enabled for non-amd64 platforms, and attempting to use it is blocked. This is because the majority of buildpacks are not cross-platform compatible, and thus building apps will either be considerably slower - due to emulating the amd64 platform - or won't work - due to building amd64 packages on arm64 platforms. -By default, this will overwrite the _first_ buildpack specified. To specify an index, the `--index` flag may be used. This flag is specified starting at a 1-index value, and defaults to `1`. +To force-enable herokuish on non-amd64 platforms, the `allowed` property can be set via `builder-herokuish:set`. The default value depends on the host platform architecture (`true` on amd64, `false` otherwise). ```shell -# the following are equivalent commands -dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git -dokku buildpacks:set --index 1 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +dokku builder-herokuish:set node-js-app allowed true ``` -If the index specified is larger than the number of buildpacks currently configured, the buildpack will be appended to the end of the list. +The default value may be set by passing an empty value for the option: ```shell -dokku buildpacks:set --index 99 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +dokku builder-herokuish:set node-js-app allowed ``` -### Removing a buildpack - -> At least one of a buildpack or index must be specified - -A single buildpack can be removed by name via the `buildpacks:remove` command. +The `allowed` property can also be set globally. The global default is platform-dependent, and the global value is used when no app-specific value is set. ```shell -dokku buildpacks:remove node-js-app https://github.com/heroku/heroku-buildpack-ruby.git +dokku builder-herokuish:set --global allowed true ``` -Buildpacks can also be removed by index via the `--index` flag. This flag is specified starting at a 1-index value. +The default value may be set by passing an empty value for the option. ```shell -dokku buildpacks:remove node-js-app --index 1 +dokku builder-herokuish:set --global allowed ``` -### Clearing all buildpacks +### Displaying builder-herokuish reports for an app -> This does not affect automatically detected buildpacks, nor does it impact any specified `BUILDPACK_URL` environment variable. +> [!IMPORTANT] +> New as of 0.29.0 -The `buildpacks:clear` command can be used to clear all configured buildpacks for a specified app. +You can get a report about the app's storage status using the `builder-herokuish:report` command: ```shell -dokku buildpacks:clear node-js-app +dokku builder-herokuish:report ``` -### Customizing the Buildpack stack builder - -> New as of 0.23.0 - -The default stack builder in use by Herokuish buildpacks in Dokku is based on `gliderlabs/herokuish:latest`. Typically, this is installed via an OS package which pulls the requisite Docker image. Users may desire to switch the stack builder to a custom version, either to update the operating system or to customize packages included with the stack builder. This can be performed via the `buildpacks:set-property` command. - -```shell -dokku buildpacks:set-property node-js-app stack gliderlabs/herokuish:latest -``` - -The specified stack builder can also be unset by omitting the name of the stack builder when calling `buildpacks:set-property`. - -```shell -dokku buildpacks:set-property node-js-app stack ``` - -A change in the stack builder value will execute the `post-stack-set` trigger. - -Finally, stack builders can be set or unset globally as a fallback. This will take precedence over a globally set `DOKKU_IMAGE` environment variable (`gliderlabs/herokuish:latest-20` by default). - -```shell -# set globally -dokku buildpacks:set-property --global stack gliderlabs/herokuish:latest - -# unset globally -dokku buildpacks:set-property --global stack +=====> node-js-app builder-herokuish information + Builder herokuish computed allowed: false + Builder herokuish global allowed: + Builder herokuish allowed: false +=====> python-sample builder-herokuish information + Builder herokuish computed allowed: true + Builder herokuish global allowed: + Builder herokuish allowed: +=====> ruby-sample builder-herokuish information + Builder herokuish computed allowed: true + Builder herokuish global allowed: + Builder herokuish allowed: ``` -### Displaying buildpack reports for an app +The `allowed` and `global-allowed` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-allowed` key holds the effective value used at build time, falling back to the global value (where one has been set) and then to the built-in default (`true` on amd64, `false` otherwise). -You can get a report about the app's buildpacks status using the `buildpacks:report` command: +You can run the command for a specific app also. ```shell -dokku buildpacks:report +dokku builder-herokuish:report node-js-app ``` ``` -=====> node-js-app buildpacks information - Buildpacks computed stack: gliderlabs/herokuish:v0.5.23-20 - Buildpacks global stack: gliderlabs/herokuish:latest-20 - Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git - Buildpacks stack: gliderlabs/herokuish:v0.5.23-20 -=====> python-sample buildpacks information - Buildpacks computed stack: gliderlabs/herokuish:latest-20 - Buildpacks global stack: gliderlabs/herokuish:latest-20 - Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git,https://github.com/heroku/heroku-buildpack-python.git - Buildpacks stack: -=====> ruby-sample buildpacks information - Buildpacks computed stack: gliderlabs/herokuish:latest-20 - Buildpacks global stack: gliderlabs/herokuish:latest-20 - Buildpacks list: - Buildpacks stack: +=====> node-js-app builder-herokuish information + Builder herokuish computed allowed: false + Builder herokuish global allowed: + Builder herokuish allowed: false ``` -You can run the command for a specific app also. +You can pass flags which will output only the value of the specific information you want. For example: ```shell -dokku buildpacks:report node-js-app +dokku builder-herokuish:report node-js-app --builder-herokuish-allowed ``` ``` -=====> node-js-app buildpacks information - Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git -``` - -You can pass flags which will output only the value of the specific information you want. For example: - -```shell -dokku buildpacks:report node-js-app --buildpacks-list +false ``` ## Errata @@ -218,63 +161,61 @@ dokku buildpacks:report node-js-app --buildpacks-list If an application was previously deployed via Dockerfile, the following commands should be run before a buildpack deploy will succeed: ```shell -dokku config:unset --no-restart node-js-app DOKKU_PROXY_PORT_MAP +dokku ports:clear node-js-app ``` -### Using a specific buildpack version +### `curl` build timeouts -> Always remember to pin your buildpack versions when using the multi-buildpacks method, or you may find deploys changing your deployed environment. +Certain buildpacks may time out in retrieving dependencies via `curl`. This can happen when your network connection is poor or if there is significant network congestion. You may see a message similar to `gzip: stdin: unexpected end of file` after a `curl` command. -By default, Dokku uses the [gliderlabs/herokuish](https://github.com/gliderlabs/herokuish/) project, which pins all of it's vendored buildpacks. There may be occasions where the pinned version results in a broken deploy, or does not have a particular feature that is required to build your project. To use a more recent version of a given buildpack, the buildpack may be specified _without_ a Git commit SHA like so: +If you see output similar this when deploying , you may need to override the `curl` timeouts to increase the length of time allotted to those tasks. You can do so via the `config` plugin: ```shell -# using the latest nodejs buildpack -dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs +dokku config:set --global CURL_TIMEOUT=1200 +dokku config:set --global CURL_CONNECT_TIMEOUT=180 ``` -This will use the latest commit on the `master` branch of the specified buildpack. To pin to a newer version of a buildpack, a sha may also be specified by using the form `REPOSITORY_URL#COMMIT_SHA`, where `COMMIT_SHA` is any tree-ish git object - usually a git tag. +### Clearing buildpack cache -```shell -# using v87 of the nodejs buildpack -dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs#v87 -``` +See the [repository management documentation](/docs/advanced-usage/repository-management.md#clearing-app-cache) for more information on how to clear buildpack build cache for an application. ### Specifying commands via Procfile -While many buildpacks have a default command that is run when a detected repository is pushed, it is possible to override this command via a Procfile. A Procfile can also be used to specify multiple commands, each of which is subject to process scaling. See the [process scaling documentation](/docs/processes/process-management.md) for more details around scaling individual processes. +See the [Procfile documentation](/docs/processes/process-management.md#procfile) for more information on how to specify different processes for your app. -A Procfile is a file named `Procfile`. It should be named `Procfile` exactly, and not anything else. For example, `Procfile.txt` is not valid. The file should be a simple text file. +### Listing Buildpacks in Use -The file must be placed in the root directory of your application. It will not function if placed in a subdirectory. +See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#listing-buildpacks-in-use) for more information on how to list buildpacks in use. -If the file exists, it should not be empty, as doing so may result in a failed deploy. +### Adding custom buildpacks -The syntax for declaring a `Procfile` is as follows. Note that the format is one process type per line, with no duplicate process types. +See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#adding-custom-buildpacks) for more information on how to add custom buildpacks. -``` -: -``` +### Overwriting a buildpack position -If, for example, you have multiple queue workers and wish to scale them separately, the following would be a valid way to work around the requirement of not duplicating process types: +See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#overwriting-a-buildpack-position) for more information on how to overwrite a buildpack position. -```Procfile -worker: env QUEUE=* bundle exec rake resque:work -importantworker: env QUEUE=important bundle exec rake resque:work -``` +### Removing a buildpack -The `web` process type holds some significance in that it is the only process type that is automatically scaled to `1` on the initial application deploy. See the [process scaling documentation](/docs/processes/process-management.md) for more details around scaling individual processes. +See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#removing-a-buildpack) for more information on how to remove a buildpack. -### `curl` build timeouts +### Clearing all buildpacks -Certain buildpacks may time out in retrieving dependencies via `curl`. This can happen when your network connection is poor or if there is significant network congestion. You may see a message similar to `gzip: stdin: unexpected end of file` after a `curl` command. +See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#clearing-all-buildpacks) for more information on how to clear all buildpacks. -If you see output similar this when deploying , you may need to override the `curl` timeouts to increase the length of time allotted to those tasks. You can do so via the `config` plugin: +### Using a specific buildpack version -```shell -dokku config:set --global CURL_TIMEOUT=1200 -dokku config:set --global CURL_CONNECT_TIMEOUT=180 -``` +See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#using-a-specific-buildpack-version) for more information on how to using a specific buildpack version -### Clearing buildpack cache -See the [repository management documentation](/docs/advanced-usage/repository-management.md#clearing-app-cache) for more information on how to clear buildpack build cache for an application. +### Displaying buildpack reports for an app + +See the [buildpack management documentation](/docs/deployment/builders/buildpack-management.md#displaying-buildpack-reports-for-an-app) for more information on how to display buildpack reports for an app. + +## Properties + +### Settable properties + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `allowed` | app + global | `true` | `--builder-herokuish-allowed`, `--builder-herokuish-global-allowed`, `--builder-herokuish-computed-allowed` | When `false`, the herokuish builder is skipped during builder detection for this app | \ No newline at end of file diff --git a/docs/deployment/builders/lambda.md b/docs/deployment/builders/lambda.md new file mode 100644 index 00000000000..114f486fdb6 --- /dev/null +++ b/docs/deployment/builders/lambda.md @@ -0,0 +1,130 @@ +# Lambda Builder + +> [!IMPORTANT] +> New as of 0.28.0 + +``` +builder-lambda:report [] [] # Displays a builder-lambda report for one or more apps +builder-lambda:set () # Set or clear a builder-lambda property for an app +``` + +The `lambda` builder builds AWS Lambda functions in an environment simulating AWS Lambda runtimes via [lambda-builder](https://github.com/dokku/lambda-builder). Apps built via this builder can run natively in Dokku and may also have their artifacts scheduled to Lambda via an appropriate scheduler. + +## Usage + +### Detection + +This builder will be auto-detected in the following case: + +- A `lambda.yml` exists in the root of the app repository. + +The builder may also be selected via the `builder:set` command + +```shell +dokku builder:set node-js-app selected lambda +``` + +### Supported languages + +The `lambda` builder plugin supports the following AWS runtime languages on Amazon Linux 2: + +- dotnet +- go (builder is based on AL1) +- nodejs +- python +- ruby + +See the [lambda-builder](https://github.com/dokku/lambda-builder#how-does-it-work) documentation for more information on how specific languages are detected. + +### Customizing the build environment + +The `lambda` builder plugin delegates all build logic to [lambda-builder](https://github.com/dokku/lambda-builder), including language detection and build/runtime environment specification. The lambda-builder tool supports a `lambda.yml` file format for customizing how apps are built. Please see the readme for lambda-builder for more information on different options. + +### Changing the `lambda.yml` location + +The `lambda.yml` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `lambdayml-path` property: + +```shell +dokku builder-lambda:set node-js-app lambdayml-path .dokku/lambda.yml +``` + +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, the build will fail. + +The default value may be set by passing an empty value for the option: + +```shell +dokku builder-lambda:set node-js-app lambdayml-path +``` + +The `lambdayml-path` property can also be set globally. The global default is `lambda.yml`, and the global value is used when no app-specific value is set. + +```shell +dokku builder-lambda:set --global lambdayml-path lambda2.yml +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku builder-lambda:set --global lambdayml-path +``` + +### Displaying builder-lambda reports for an app + +You can get a report about the app's storage status using the `builder-lambda:report` command: + +```shell +dokku builder-lambda:report +``` + +``` +=====> node-js-app builder-lambda information + Builder lambda computed lambdayml path: lambda2.yml + Builder lambda global lambdayml path: + Builder lambda lambdayml path: lambda2.yml +=====> python-sample builder-lambda information + Builder lambda computed lambdayml path: lambda.yml + Builder lambda global lambdayml path: + Builder lambda lambdayml path: +=====> ruby-sample builder-lambda information + Builder lambda computed lambdayml path: lambda.yml + Builder lambda global lambdayml path: + Builder lambda lambdayml path: +``` + +The `lambdayml-path` and `global-lambdayml-path` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-lambdayml-path` key holds the effective value used at build time, falling back to the global value (where one has been set) and then to the built-in default of `lambda.yml`. + +You can run the command for a specific app also. + +```shell +dokku builder-lambda:report node-js-app +``` + +``` +=====> node-js-app builder-lambda information + Builder lambda computed lambdayml path: lambda2.yml + Builder lambda global lambdayml path: + Builder lambda lambdayml path: lambda2.yml +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku builder-lambda:report node-js-app --builder-lambda-lambdayml-path +``` + +``` +lambda2.yml +``` + +## Properties + +### Settable properties + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `lambdayml-path` | app + global | `lambda.yml` | `--builder-lambda-lambdayml-path`, `--builder-lambda-global-lambdayml-path`, `--builder-lambda-computed-lambdayml-path` | Path within the app to the `lambda.yml` manifest used by the lambda builder | diff --git a/docs/deployment/builders/nixpacks.md b/docs/deployment/builders/nixpacks.md new file mode 100644 index 00000000000..2d441c7aae0 --- /dev/null +++ b/docs/deployment/builders/nixpacks.md @@ -0,0 +1,143 @@ +# Nixpacks + +> [!IMPORTANT] +> New as of 0.32.0 + +The `nixpacks` builder builds apps via [Nixpacks](https://nixpacks.com/), a buildpack alternative. + +## Usage + +### Requirements + +The `nixpacks` cli tool is not included by default with Dokku or as a dependency. It must also be installed as shown on [this page](https://nixpacks.com/docs/install#debian-(and-derivatives-like-ubuntu)). + +Builds will proceed with the `nixpacks` cli for the app from then on. + +### Detection + +This builder will be auto-detected in the following case: + +- A `nixpacks.toml` exists in the root of the app repository. + +The builder may also be selected via the `builder:set` command + +```shell +dokku builder:set node-js-app selected nixpacks +``` + +### Supported languages + +See the [upstream nixpacks documentation](https://nixpacks.com/docs) for further information on what languages and frameworks are supported. + +### Build-time configuration variables + +For security reasons - and as per [Docker recommendations](https://github.com/docker/docker/issues/13490) - nixpacks-based deploys have variables available only during runtime. + +For users that require customization in the `build` phase, you may use build arguments via the [docker-options plugin](/docs/advanced-usage/docker-options.md). All environment variables set by the `config` plugin are automatically exported within the nixpacks build environment, and thus `--env` only requires setting a key without a value. + +```shell +dokku docker-options:add node-js-app build '--env NODE_ENV' +``` + +Alternatively, a full value may be provided in the form of `--env KEY=VALUE`: + +```shell +dokku docker-options:add node-js-app build '--env NODE_ENV=production' +``` + +### Changing the `nixpacks.toml` location + +The `nixpacks.toml` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `nixpackstoml-path` property: + +```shell +dokku builder-nixpacks:set node-js-app nixpackstoml-path .dokku/nixpacks.toml +``` + +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, the build will fail. + +The default value may be set by passing an empty value for the option: + +```shell +dokku builder-nixpacks:set node-js-app nixpackstoml-path +``` + +The `nixpackstoml-path` property can also be set globally. The global default is `nixpacks.toml`, and the global value is used when no app-specific value is set. + +```shell +dokku builder-nixpacks:set --global nixpackstoml-path nixpacks2.toml +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku builder-nixpacks:set --global nixpackstoml-path +``` + +### Disabling cache + +Disable cache using the [docker-options] plugin: + +```shell +dokku docker-options:add node-js-app build "--no-cache" +``` + +### Displaying builder-nixpacks reports for an app + +You can get a report about the app's storage status using the `builder-nixpacks:report` command: + +```shell +dokku builder-nixpacks:report +``` + +``` +=====> node-js-app builder-nixpacks information + Builder-nixpacks computed nixpackstoml path: nixpacks2.toml + Builder-nixpacks global nixpackstoml path: + Builder-nixpacks nixpackstoml path: nixpacks2.toml +=====> python-sample builder-nixpacks information + Builder-nixpacks computed nixpackstoml path: nixpacks.toml + Builder-nixpacks global nixpackstoml path: + Builder-nixpacks nixpackstoml path: +=====> ruby-sample builder-nixpacks information + Builder-nixpacks computed nixpackstoml path: nixpacks.toml + Builder-nixpacks global nixpackstoml path: + Builder-nixpacks nixpackstoml path: +``` + +The `nixpackstoml-path` and `global-nixpackstoml-path` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-nixpackstoml-path` key holds the effective value used at build time, falling back to the global value (where one has been set) and then to the built-in default of `nixpacks.toml`. + +You can run the command for a specific app also. + +```shell +dokku builder-nixpacks:report node-js-app +``` + +``` +=====> node-js-app builder-nixpacks information + Builder-nixpacks computed nixpackstoml path: nixpacks2.toml + Builder-nixpacks global nixpackstoml path: + Builder-nixpacks nixpackstoml path: nixpacks2.toml +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku builder-nixpacks:report node-js-app --builder-nixpacks-nixpackstoml-path +``` + +``` +nixpacks2.toml +``` + +## Properties + +### Settable properties + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `nixpackstoml-path` | app + global | `nixpacks.toml` | `--builder-nixpacks-nixpackstoml-path`, `--builder-nixpacks-global-nixpackstoml-path`, `--builder-nixpacks-computed-nixpackstoml-path` | Path within the app to the `nixpacks.toml` manifest used by the nixpacks builder | diff --git a/docs/deployment/builders/null.md b/docs/deployment/builders/null.md index 88a1b2f2dd7..070d1fb0e67 100644 --- a/docs/deployment/builders/null.md +++ b/docs/deployment/builders/null.md @@ -1,8 +1,9 @@ # Null Builder +> [!IMPORTANT] > New as of 0.25.0 -The `null` builder does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ip:port-combination) for more information on the aforementioned use case. +The `null` builder does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ipport-combination) for more information on the aforementioned use case. ## Usage diff --git a/docs/deployment/builders/railpack.md b/docs/deployment/builders/railpack.md new file mode 100644 index 00000000000..ac289b5360a --- /dev/null +++ b/docs/deployment/builders/railpack.md @@ -0,0 +1,152 @@ +# Railpack + +> [!IMPORTANT] +> New as of 0.37.0 + +The `railpack` builder builds apps via [Railpack](https://railpack.com/), a buildpack alternative. + +## Usage + +### Requirements + +Before using Railpacks, the following steps must be taken: + +- Install the `railpack` cli: The `railpack` cli tool is not included by default with Dokku or as a dependency. It must also be installed as shown on [this page](https://railpack.com/installation). +- Create a `buildkit` builder: Railpack uses buildkit. + ```shell + docker run --rm --privileged -d --name buildkit moby/buildkit + ``` +- Set the buildkit builder: Update the `/etc/default/dokku` file to set `BUILDKIT_HOST`: + ```shell + touch /etc/default/dokku + echo "export BUILDKIT_HOST='docker-container://buildkit'" >> /etc/default/dokku + ``` + +### Detection + +This builder will be auto-detected in the following case: + +- A `railpack.json` exists in the root of the app repository. + +The builder may also be selected via the `builder:set` command + +```shell +dokku builder:set node-js-app selected railpack +``` + +### Supported languages + +See the [upstream railpack documentation](https://railpack.com/) for further information on what languages and frameworks are supported. + +### Build-time configuration variables + +For security reasons - and as per [Docker recommendations](https://github.com/docker/docker/issues/13490) - railpack-based deploys have variables available only during runtime. + +For users that require customization in the `build` phase, you may use build arguments via the [docker-options plugin](/docs/advanced-usage/docker-options.md). All environment variables set by the `config` plugin are automatically exported within the railpack build environment, and thus `--env` only requires setting a key without a value. + +```shell +dokku docker-options:add node-js-app build '--env NODE_ENV' +``` + +Alternatively, a full value may be provided in the form of `--env KEY=VALUE`: + +```shell +dokku docker-options:add node-js-app build '--env NODE_ENV=production' +``` + +### Changing the `railpack.json` location + +The `railpack.json` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `railpackjson-path` property: + +```shell +dokku builder-railpack:set node-js-app railpackjson-path .dokku/railpack.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, the build will fail. + +The default value may be set by passing an empty value for the option: + +```shell +dokku builder-railpack:set node-js-app railpackjson-path +``` + +The `railpackjson-path` property can also be set globally. The global default is `railpack.json`, and the global value is used when no app-specific value is set. + +```shell +dokku builder-railpack:set --global railpackjson-path railpack2.json +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku builder-railpack:set --global railpackjson-path +``` + +### Disabling cache + +Disable cache using the [docker-options] plugin: + +```shell +dokku docker-options:add node-js-app build "--no-cache" +``` + +### Displaying builder-railpack reports for an app + +You can get a report about the app's storage status using the `builder-railpack:report` command: + +```shell +dokku builder-railpack:report +``` + +``` +=====> node-js-app builder-railpack information + Builder-railpack computed railpackjson path: railpack2.json + Builder-railpack global railpackjson path: + Builder-railpack railpackjson path: railpack2.json +=====> python-sample builder-railpack information + Builder-railpack computed railpackjson path: railpack.json + Builder-railpack global railpackjson path: + Builder-railpack railpackjson path: +=====> ruby-sample builder-railpack information + Builder-railpack computed railpackjson path: railpack.json + Builder-railpack global railpackjson path: + Builder-railpack railpackjson path: +``` + +The `railpackjson-path` and `global-railpackjson-path` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-railpackjson-path` key holds the effective value used at build time, falling back to the global value (where one has been set) and then to the built-in default of `railpack.json`. + +You can run the command for a specific app also. + +```shell +dokku builder-railpack:report node-js-app +``` + +``` +=====> node-js-app builder-railpack information + Builder-railpack computed railpackjson path: railpack2.json + Builder-railpack global railpackjson path: + Builder-railpack railpackjson path: railpack2.json +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku builder-railpack:report node-js-app --builder-railpack-railpackjson-path +``` + +``` +railpack2.json +``` + +## Properties + +### Settable properties + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `railpackjson-path` | app + global | `railpack.json` | `--builder-railpack-railpackjson-path`, `--builder-railpack-global-railpackjson-path`, `--builder-railpack-computed-railpackjson-path` | Path within the app to the `railpack.json` manifest used by the railpack builder | diff --git a/docs/deployment/continuous-integration/generic.md b/docs/deployment/continuous-integration/generic.md index 22154a92327..2e9692a6a89 100644 --- a/docs/deployment/continuous-integration/generic.md +++ b/docs/deployment/continuous-integration/generic.md @@ -11,6 +11,7 @@ Assuming a Docker image can be run as a CI task with environment variables injec - [gitlab-ci](https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/) - [semaphoreci](https://semaphoreci.com/) - [travisci](https://travis-ci.com/) +- [woodpecker ci](https://woodpecker-ci.org/) ## Simple Usage diff --git a/docs/deployment/continuous-integration/github-actions.md b/docs/deployment/continuous-integration/github-actions.md index cb3cd56403f..0a052d7324e 100644 --- a/docs/deployment/continuous-integration/github-actions.md +++ b/docs/deployment/continuous-integration/github-actions.md @@ -13,10 +13,10 @@ on: jobs: deploy: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Cloning repo - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/docs/deployment/continuous-integration/woodpecker-ci.md b/docs/deployment/continuous-integration/woodpecker-ci.md new file mode 100644 index 00000000000..4c6fcda6a2b --- /dev/null +++ b/docs/deployment/continuous-integration/woodpecker-ci.md @@ -0,0 +1,29 @@ +# Woodpecker CI + +[Woodpecker CI](https://woodpecker-ci.org/docs/intro) can be used to automatically deploy a Dokku application via the official [dokku/ci-docker-image](https://github.com/dokku/ci-docker-image). The simplest example is as follows: + +```yaml +when: + - event: push + branch: main + +clone: + git: + image: woodpeckerci/plugin-git + settings: + partial: false + depth: 0 + +steps: + - name: deploy + image: dokku/ci-docker-image + environment: + GIT_REMOTE_URL: ssh://dokku@dokku.me:22/appname + SSH_PRIVATE_KEY: + from_secret: DOKKU_ME_SSH_DEPLOY_KEY + commands: + - dokku-deploy +``` + +- The standard `clone` stage is replaced with one that performs a complete clone of the repository (the default is a shallow clone, which dokku would reject). +- A `GIT_REMOTE_URL` and a `SSH_PRIVATE_KEY` (with help of the `from_secret` directive) suffice to carry out a deployment. \ No newline at end of file diff --git a/docs/deployment/logs.md b/docs/deployment/logs.md index 887ecf0ba38..1a56ca3f24f 100644 --- a/docs/deployment/logs.md +++ b/docs/deployment/logs.md @@ -1,8 +1,8 @@ # Log Management ``` -logs [-h] [-t|--tail] [-n|--num num] [-q|--quiet] [-p|--ps process] # Display recent log output -logs:failed [--all|] # Shows the last failed deploy logs +logs [-h|--help] [-t|--tail] [-n|--num num] [-q|--quiet] [-p|--ps process] # Display recent log output +logs:failed --all| # Shows the last failed deploy logs logs:report [] [] # Displays a logs report for one or more apps logs:set [--global|] # Set or clear a logs property for an app logs:vector-logs [--num num] [--tail] # Display vector log output @@ -20,6 +20,8 @@ You can easily get logs of an app using the `logs` command: dokku logs node-js-app ``` +Logs are pulled via integration with the scheduler for the specified application via "live tailing". As such, logs from previously running deployments are usually not available. Users that desire to see logs from previous deployments for debugging purposes should persist those logs to external services. Please see Dokku's [vector integration](/docs/deployment/logs.md#vector-logging-shipping) for more information on how to persist logs across deployments to ship logs to another service or a third-party platform. + #### Behavioral modifiers Dokku also supports certain command-line arguments that augment the `log` command's behavior. @@ -41,7 +43,8 @@ The above command will show logs continually from the web process. ### Failed deploy logs -> Warning: The default [docker-local scheduler](/docs/advanced-usage/schedulers/docker-local.md) will "store" these until the next deploy or until the old containers are garbage collected - whichever runs first. If you require the logs beyond this point in time, please ship the logs to a centralized log server. +> [!WARNING] +> The default [docker-local scheduler](/docs/deployment/schedulers/docker-local.md) will "store" these until the next deploy or until the old containers are garbage collected - whichever runs first. If you require the logs beyond this point in time, please ship the logs to a centralized log server. In some cases, it may be useful to retrieve the logs from a previously failed deploy. @@ -87,13 +90,15 @@ dokku logs:set --global max-size ### Vector Logging Shipping +> [!IMPORTANT] > New as of 0.22.6 Vector is an open-source, lightweight and ultra-fast tool for building observability pipelines. Dokku integrates with it for shipping container logs for the `docker-local` scheduler. Users may configure log-shipping on a per-app or global basis, neither of which interfere with the `dokku logs` commands. #### Starting the Vector container -> Warning: While the default vector image may be updated over time, this will not impact running vector containers. Users are encouraged to view any Dokku and Vector changelogs to ensure their system will continue running as expected. +> [!WARNING] +> While the default vector image may be updated over time, this will not impact running vector containers. Users are encouraged to view any Dokku and Vector changelogs to ensure their system will continue running as expected. Vector may be started via the `logs:vector-start` command. @@ -111,10 +116,12 @@ dokku logs:vector-start --vector-image timberio/vector:latest-debian The `vector` container will be started with the following volume mounts: -- `/var/lib/dokku/data/logs/vector.json:/etc/vector/vector.json` +- `/var/lib/dokku/data/logs:/etc/vector` - `/var/run/docker.sock:/var/run/docker.sock` - `/var/log/dokku/apps:/var/log/dokku/apps` +The `/etc/vector` mount includes the `vector.json` configuration file, but also may be used to provide extra files to the vector container. + The final volume mount - `/var/log/dokku/apps` - may be used for users that wish to ship logs to a file on disk that may be later logrotated. This directory is owned by the `dokku` user and group, with permissions set to `0755`. At this time, log-rotation is not configured for this directory. #### Stopping the Vector container @@ -150,6 +157,60 @@ dokku logs:vector-logs --tail --num 10 The above command will show logs continually from the vector container, with an initial history of 10 log lines +#### Changing the vector image + +Dokku integrates with a Vector docker image version that is known to be compatible with the documentation. In some cases, it may be useful to specify an alternative image version. To do so, set the global `vector-image` property. + +```shell +dokku logs:set --global vector-image timberio/vector:0.35.X-debian +``` + +Once set, the vector container will need to be stopped and then started. Note that specifying the `--vector-image` flag on the `logs:vector-start` will override the setting. + +```shell +dokku logs:vector-stop +dokku logs:vector-start +``` + +Setting this to an empty string will reset the version to the version currently compatible with the Dokku installation. + +```shell +dokku logs:set --global vector-image +``` + +#### Attaching Vector to additional Docker networks + +By default, the Vector container runs with `network_mode: bridge` and can only reach app containers that are also on the default bridge network. Apps deployed onto a per-app network or a custom network - typically via `dokku network:set initial-network ` - are not reachable from Vector over Docker's internal DNS, so sinks that need to talk to those apps directly (for example, an in-host log search service such as [Logpond](https://github.com/dokku/logpond)) would have to route traffic out through the external proxy. + +The global `vector-networks` property accepts a comma-separated list of Docker networks for Vector to join. + +```shell +dokku logs:set --global vector-networks dokku-logs +``` + +Multiple networks may be specified by separating them with a comma. + +```shell +dokku logs:set --global vector-networks dokku-logs,observability +``` + +Setting this property **replaces** the default bridge attachment: the Vector container will be on the configured user-defined networks only, not the default Docker `bridge` network. Outbound traffic continues to work through the user-defined networks' NAT, so external sinks such as Datadog or hosted HTTP endpoints remain reachable. + +Each network must already exist; setting a non-existent network or the reserved `bridge` value will fail. The list can be cleared by setting an empty value, which restores the default `network_mode: bridge` configuration. + +```shell +dokku logs:set --global vector-networks +``` + +Network attachments are reconciled by `docker compose` on every `logs:vector-start`, so after changing the value the Vector container must be cycled. + +```shell +dokku logs:vector-stop +dokku logs:vector-start +``` + +Once attached, an app on `dokku-logs` (for example via `dokku network:set node-js-app initial-network dokku-logs`) is reachable from Vector at `.:` over the shared network without round-tripping through the external proxy. + #### Configuring a log sink Vector uses the concept of log "sinks" to send logs to a given endpoint. Log sinks may be configured globally or on a per-app basis by specifying a `vector-sink` in DSN form with the `logs:set` command. Specifying a sink value will reload any running vector container. @@ -206,4 +267,62 @@ For some sinks - such as the `http` sink - it may be useful to use special chara dokku logs:set test vector-sink "http://?uri=https%3A//loggerservice.com%3A1234/%3Ftoken%3Dabc1234%26type%3Dvector" ``` -Please read the [sink documentation](https://vector.dev/docs/reference/sinks/) for your sink of choice to configure the sink as desired. +For kubernetes, it may be necessary to use template syntax - `{{ .parent.child }}` - in the sink configuration. Naively using brackets will fail due to the Helm chart install process assuming that the template should be interpreted at Helm install time vs by Vector itself. To avoid this, use `base64enc:` values (available only for top-level properties at this time). The following example shows how to use `{{ pod }}` as a value. + +```shell +# encode the value with a Helm `print` statement wrapper +encoded="$(echo '{{ print "{{ pod }}" }}' | base64)" +# the value of encoded should be: e3sgcHJpbnQgInt7IHBvZCB9fSIgfX0K + +# set the value +dokku logs:set test vector-sink "http://?process=base64enc%3A${encoded}" +``` + +This will transform the value to it's encoded form when configuring Vector sinks for Kubernetes. + +Please read the [sink documentation](https://vector.dev/docs/reference/configuration/sinks/) for your sink of choice to configure the sink as desired. + +##### Configuring the app label + +Logs shipped by vector include the label `com.dokku.app-name`, which is an alias for the app name. This can be changed via the `app-label-alias` logs property with the `logs:set` command. Specifying a new alias will reload any running vector container. + +```shell +# setting the sink value in quotes is encouraged to avoid +# issues with ampersand encoding in shell commands +dokku logs:set node-js-app app-label-alias "app-name" +``` + +An alias may be removed by setting an empty value, which will also reload the running vector container. + +```shell +dokku logs:set node-js-app app-label-alias +``` + +Only one alias may be specified on a per-app basis at a given time. + +App label aliases can also be specified globally by specifying the `--global` flag to `logs:set` with no app name specified: + +```shell +dokku logs:set --global app-label-alias "app-name" +``` + +As with app-specific label alias settings, the global value may also be cleared by setting no value. + +```shell +dokku logs:set --global app-label-alias +``` + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `logs:report`. The JSON keys emitted by `logs:report --format json` are the same names with the leading `--logs-` stripped (e.g. `max-size`, `global-max-size`, `computed-max-size`). Legacy keys with the `logs-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `app-label-alias` | app + global | `com.dokku.app-name` | `--logs-app-label-alias`, `--logs-global-app-label-alias`, `--logs-computed-app-label-alias` | Docker label key whose value is used to identify the app when shipping logs | +| `max-size` | app + global | `10m` | `--logs-max-size`, `--logs-global-max-size`, `--logs-computed-max-size` | Maximum size of an individual log file before rotation | +| `vector-image` | global only | _parsed from `plugins/logs/Dockerfile`_ | `--logs-global-vector-image`, `--logs-computed-vector-image` | Docker image used to run the vector log-shipper container | +| `vector-networks` | global only | none | `--logs-global-vector-networks`, `--logs-computed-vector-networks` | Comma-separated list of docker networks the vector container is attached to | +| `vector-sink` | app + global | none | `--logs-vector-sink`, `--logs-global-vector-sink`, `--logs-computed-vector-sink` | DSN-style sink configuration for vector (e.g. `console://` or `loki://...`) | diff --git a/docs/deployment/methods/archive.md b/docs/deployment/methods/archive.md new file mode 100644 index 00000000000..52196c5a509 --- /dev/null +++ b/docs/deployment/methods/archive.md @@ -0,0 +1,60 @@ +# Tar and Zip Archives + +## Initializing an app repository from an archive file + +> [!IMPORTANT] +> New as of 0.24.0 + +A Dokku app repository can be initialized or updated from the contents of an archive file via the `git:from-archive` command. This is an excellent way of tracking changes when deploying pre-built binary archives, such as java jars or go binaries. This can also be useful when deploying directly from a GitHub repository at a specific commit. + +```shell +dokku git:from-archive node-js-app https://github.com/dokku/smoke-test-app/releases/download/2.0.0/smoke-test-app.tar +``` + +In the above example, Dokku will build the app as if the repository contained the extracted contents of the specified archive file. + +Triggering a build with the same archive file multiple times will result in Dokku exiting `0` early as there will be no changes detected. + +The `git:from-archive` command can optionally take a git `user.name` and `user.email` argument (in that order) to customize the author. If the arguments are left empty, they will fallback to `Dokku` and `automated@dokku.sh`, respectively. + +```shell +dokku git:from-archive node-js-app https://github.com/dokku/smoke-test-app/releases/download/2.0.0/smoke-test-app.tar "Camila" "camila@example.com" +``` + +The default archive type is always set to `.tar`. To use a different archive type, specify the `--archive-type` flag. Failure to do so will result in a failure to extract the archive. + +```shell +dokku git:from-archive --archive-type zip node-js-app https://github.com/dokku/smoke-test-app/archive/2.0.0.zip "Camila" "camila@example.com" +``` + +Finally, if the archive url is specified as `--`, the archive will be fetched from stdin. + +```shell +curl -sSL https://github.com/dokku/smoke-test-app/releases/download/2.0.0/smoke-test-app.tar | dokku git:from-archive node-js-app -- +``` + +## Archive Safety + +Archive contents are validated before extraction to prevent path traversal and symlink escape attacks. Archives containing absolute paths, parent directory traversal entries (`..`), or symlinks pointing outside the extraction directory are rejected. + +The following limits can be configured via global git properties: + +- `archive-max-size` - maximum archive size in bytes (default: `1073741824`, 1 GiB) +- `archive-max-files` - maximum number of entries in an archive (default: `10000`) + +```shell +dokku git:set --global archive-max-size 2147483648 +dokku git:set --global archive-max-files 20000 +``` + +Unset a property to restore the default: + +```shell +dokku git:set --global archive-max-size +``` + +The current values can be inspected via the global git report: + +```shell +dokku git:report --global +``` diff --git a/docs/deployment/methods/git.md b/docs/deployment/methods/git.md index 5730f14aa4c..64a7e61c361 100644 --- a/docs/deployment/methods/git.md +++ b/docs/deployment/methods/git.md @@ -1,18 +1,22 @@ # Git Deployment +> [!IMPORTANT] > Subcommands new as of 0.12.0 ``` git:allow-host # Adds a host to known_hosts git:auth [ ] # Configures netrc authentication for a given git server +git:auth-status [ ] # Reports whether the netrc entry matches the requested state git:from-archive [--archive-type ARCHIVE_TYPE] [ ] # Updates an app's git repository with a given archive file git:from-image [--build-dir DIRECTORY] [ ] # Updates an app's git repository with a given docker image -git:sync [--build] [] # Clone or fetch an app from remote git repo +git:generate-deploy-key # Generates a deploy ssh key +git:load-image [--build-dir DIRECTORY] [ ] # Updates an app's git repository with a docker image loaded from stdin +git:sync [--build|--build-if-changes] [--skip-deploy-branch] [] # Clone or fetch an app from remote git repo git:initialize # Initialize a git repository for an app git:public-key # Outputs the dokku public deploy key -git:report [] [] # Displays a git report for one or more apps +git:report [] [|--format json] # Displays a git report for one or more apps git:set () # Set or clear a git property for an app -git:unlock [--force] # Removes previous git clone folder for new deployment +git:status # Show the working tree status for an app ``` Git-based deployment has been the traditional method of deploying applications in Dokku. As of v0.12.0, Dokku introduces a few ways to customize the experience of deploying via `git push`. A Git-based deployment currently supports building applications via: @@ -23,7 +27,8 @@ Git-based deployment has been the traditional method of deploying applications i ## Usage -> Warning: Pushing from a shallow clone is not currently supported and may have undefined behavior. Please unshallow your local repository before pushing to a Dokku app to avoid potential errors in the deployment process. +> [!WARNING] +> Pushing from a shallow clone is not currently supported and may have undefined behavior. Please unshallow your local repository before pushing to a Dokku app to avoid potential errors in the deployment process. ### Initializing an application @@ -36,9 +41,10 @@ When an application is created via `git push`, Dokku will create the proper `pre dokku git:initialize node-js-app ``` -In order for the above command to succeed, the application _must_ already exist. +In order for the above command to succeed, the application _must_ already exist. -> Warning: If the `pre-receive` hook was customized in any way, this will overwrite that hook with the current defaults for Dokku. +> [!WARNING] +> If the `pre-receive` hook was customized in any way, this will overwrite that hook with the current defaults for Dokku. ### Changing the deploy branch @@ -70,6 +76,7 @@ Pushing multiple branches can also be supported by creating a [receive-branch](/ ### Configuring the `GIT_REV` environment variable +> [!IMPORTANT] > New as of 0.12.0 Application deployments will include a special `GIT_REV` environment variable containing the current deployment sha being deployed. For rebuilds, this SHA will remain the same. @@ -119,151 +126,217 @@ dokku git:set node-js-app keep-git-dir "" Please keep in mind that setting `keep-git-dir` to `true` may result in unstaged changes shown within the built container due to the build process generating application changes within the built app directory. -### Initializing an app repository from a docker image +### Initializing an app repository from a remote repository -> New as of 0.24.0 +> [!IMPORTANT] +> New as of 0.23.0 -A Dokku app repository can be initialized or updated from a Docker image via the `git:from-image` command. This command will either initialize the app repository or update it to include the specified Docker image via a `FROM` stanza. This is an excellent way of tracking changes when deploying only a given docker image, especially if deploying an image from a remote CI/CD pipeline. +A Dokku app repository can be initialized or updated from a remote git repository via the `git:sync` command. This command will either clone or fetch updates from a remote repository and has undefined behavior if the history cannot be fast-fowarded to the referenced repository reference. Any repository that can be cloned by the `dokku` user can be specified. + +> The application must exist before the repository can be initialized ```shell -dokku git:from-image node-js-app dokku/node-js-getting-started:latest +dokku git:sync node-js-app https://github.com/heroku/node-js-getting-started.git ``` -In the above example, Dokku will build the app as if the repository contained _only_ a `Dockerfile` with the following content: +The `git:sync` command optionally takes an optional third parameter containing a git reference, which may be a branch, tag, or specific commit. + +```shell +# specify a branch +dokku git:sync node-js-app https://github.com/heroku/node-js-getting-started.git main + +# specify a tag +dokku git:sync node-js-app https://github.com/heroku/node-js-getting-started.git 1 -```Dockerfile -FROM dokku/node-js-getting-started:latest +# specify a commit +dokku git:sync node-js-app https://github.com/heroku/node-js-getting-started.git 97e6c72491c7531507bfc5413903e0e00e31e1b0 ``` -Triggering a build with the same arguments multiple times will result in Dokku exiting `0` early as there will be no changes detected. If the image tag is reused but the underlying image is different, it is recommended to use the image digest instead of the tag. This can be retrieved via the following command: +By default, this command does not trigger an application build. To do so during a `git:sync`, specify the `--build` flag. ```shell -docker inspect --format='{{index .RepoDigests 0}}' $IMAGE_NAME +dokku git:sync --build node-js-app https://github.com/heroku/node-js-getting-started.git ``` -The resulting `git:from-image` call would then be: +When running `git:sync` without a reference, it may be useful to only build when there are changes. To do so, specify the `--build-if-changes` flag. ```shell -# where the image sha is: sha256:9d187c3025d03c033dcc71e3a284fee53be88cc4c0356a19242758bc80cab673 -dokku git:from-image node-js-app dokku/node-js-getting-started@sha256:9d187c3025d03c033dcc71e3a284fee53be88cc4c0356a19242758bc80cab673 +dokku git:sync --build-if-changes node-js-app https://github.com/heroku/node-js-getting-started.git ``` -The `git:from-image` command can optionally take a git `user.name` and `user.email` argument (in that order) to customize the author. If the arguments are left empty, they will fallback to `Dokku` and `automated@dokku.sh`, respectively. +By default, when running `git:sync` with a git branch reference, Dokku will automatically set the `deploy-branch` property to the specified branch. To skip setting the deploy branch, specify the `--skip-deploy-branch` flag. ```shell -dokku git:from-image node-js-app dokku/node-js-getting-started:latest "Camila" "camila@example.com" +dokku git:sync --skip-deploy-branch node-js-app https://github.com/heroku/node-js-getting-started.git main ``` -If the image is a private image that requires a docker login to access, the `registry:login` command should be used to log into the registry. See the [registry documentation](/docs/advanced-usage/registry-management.md#logging-into-a-registry) for more details on this process. +### Initializing from private repositories -Finally, certain images may require a custom build context in order for `ONBUILD ADD` and `ONBUILD COPY` statements to succeed. A custom build context can be specified via the `--build-dir` flag. All files in the specified `build-dir` will be copied into the repository for use within the `docker build` process. The build context _must_ be specified on each deploy, and is not otherwise persisted between builds. +> [!IMPORTANT] +> New as of 0.24.0 -```shell -dokku git:from-image --build-dir path/to/build node-js-app dokku/node-js-getting-started:latest "Camila" "camila@example.com" -``` +Initializing from a private repository requires one of the following: -See the [dockerfile documentation](/docs/deployment/builders/dockerfiles.md) to learn about the different ways to configure Dockerfile-based deploys. +- A Public SSH Key (`id_rsa.pub` file) configured on the remote server, with the associated private key (`id_rsa`) in the Dokku server's `/home/dokku/.ssh/` directory. +- A configured [`.netrc`](https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html) entry. -### Initializing an app repository from an archive file +Dokku provides the `git:auth` command which can be used to configure a `netrc` entry for the remote server. This command can be used to add or remove configuration for any remote server. -> New as of 0.24.0 +```shell +# add credentials for github.com +dokku git:auth github.com username personal-access-token + +# remove credentials for github.com +dokku git:auth github.com +``` + +For syncing to a private repository stored on a remote Git product such as GitHub or GitLab, Dokku's recommendation is to use a personal access token on a bot user where possible. Please see your service's documentation for information regarding the recommended best practices. -A Dokku app repository can be initialized or updated from the contents of an archive file via the `git:from-archive` command. This is an excellent way of tracking changes when deploying pre-built binary archives, such as java jars or go binaries. This can also be useful when deploying directly from a GitHub repository at a specific commit. +The password for `git:auth` may also be provided over `STDIN` to avoid placing it on the command line: ```shell -dokku git:from-archive node-js-app https://github.com/dokku/smoke-test-app/releases/download/2.0.0/smoke-test-app.tar +# pipe the password into git:auth +echo "personal-access-token" | dokku git:auth github.com username ``` -In the above example, Dokku will build the app as if the repository contained the extracted contents of the specified archive file. +#### Checking the configured auth state -Triggering a build with the same archive file multiple times will result in Dokku exiting `0` early as there will be no changes detected. +> [!IMPORTANT] +> New as of 0.38.0 -The `git:from-archive` command can optionally take a git `user.name` and `user.email` argument (in that order) to customize the author. If the arguments are left empty, they will fallback to `Dokku` and `automated@dokku.sh`, respectively. +The `git:auth-status` command reports whether the configured `netrc` entry matches a desired state without exposing the underlying file. It exits `0` when the configured state matches and `1` otherwise. This allows external tooling such as configuration management systems to perform idempotent updates without reading `$DOKKU_ROOT/.netrc` directly. ```shell -dokku git:from-archive node-js-app https://github.com/dokku/smoke-test-app/releases/download/2.0.0/smoke-test-app.tar "Camila" "camila@example.com" +# check whether github.com is configured with the expected credentials +dokku git:auth-status github.com username personal-access-token + +# check whether no credentials are configured for github.com +dokku git:auth-status github.com ``` -The default archive type is always set to `.tar`. To use a different archive type, specify the `--archive-type` flag. Failure to do so will result in a failure to extract the archive. +As with `git:auth`, the password may be provided via `STDIN`: ```shell -dokku git:from-archive --archive-type zip node-js-app https://github.com/dokku/smoke-test-app/archive/2.0.0.zip "Camila" "camila@example.com" +echo "personal-access-token" | dokku git:auth-status github.com username ``` -Finally, if the archive url is specified as `--`, the archive will be fetched from stdin. +### Allowing remote repository hosts + +By default, the Dokku host may not have access to a server containing the remote repository. This can be initialized via the `git:allow-host` command. ```shell -curl -sSL https://github.com/dokku/smoke-test-app/releases/download/2.0.0/smoke-test-app.tar | dokku git:from-archive node-js-app -- +dokku git:allow-host github.com ``` -### Initializing an app repository from a remote repository +Note that this command is currently not idempotent and may add duplicate entries to the `~dokku/.ssh/known_hosts` file. -> New as of 0.23.0 +### Creating a cloning ssh key pair -A Dokku app repository can be initialized or updated from a remote git repository via the `git:sync` command. This command will either clone or fetch updates from a remote repository and has undefined behavior if the history cannot be fast-fowarded to the referenced repository reference. Any repository that can be cloned by the `dokku` user can be specified. +> [!IMPORTANT] +> New as of 0.33.0 -> The application must exist before the repository can be initialized +While most repositories can be authenticated to via the `git:auth` command, some users may prefer to use an ssh key. This can be generated via the `git:generate-deploy-key` command, which generates a passwordless ed25519 key-pair. ```shell -dokku git:sync node-js-app https://github.com/heroku/node-js-getting-started.git +dokku git:generate-deploy-key ``` -The `git:sync` command optionally takes an optional third parameter containing a git reference, which may be a branch, tag, or specific commit. +``` +Generating public/private ed25519 key pair. +Your identification has been saved in /home/dokku/.ssh/id_ed25519 +Your public key has been saved in /home/dokku/.ssh/id_ed25519.pub +The key fingerprint is: +SHA256:PvlvVfbpYvkmA87rTfLUq07e3GarRN1BcLqDSjod+p8 dokku@ubuntu +The key's randomart image is: ++--[ED25519 256]--+ +| ..o | +| + | +| . . | +| . o =| +| So . + ++| +| .=.o.. +..| +| ++oo..*o. | +| oo o%*oo=| +| .+E=BOOo| ++----[SHA256]-----+ +``` -```shell -# specify a branch -dokku git:sync node-js-app https://github.com/heroku/node-js-getting-started.git main +### Verifying the cloning public key -# specify a tag -dokku git:sync node-js-app https://github.com/heroku/node-js-getting-started.git 1 +In order to clone a remote repository, the remote server should have the Dokku host's public key configured. This plugin does not currently create this key, but if can be shown via the `git:public-key` command. -# specify a commit -dokku git:sync node-js-app https://github.com/heroku/node-js-getting-started.git 97e6c72491c7531507bfc5413903e0e00e31e1b0 +```shell +dokku git:public-key ``` -By default, this command does not trigger an application build. To do so during a `git:sync`, specify the `--build` flag. +If there is no key, an error message is shown that displays the command that can be run on the Dokku server to generate a new public/private ssh key pair. -```shell -dokku git:sync --build node-js-app https://github.com/heroku/node-js-getting-started.git -``` +### Displaying git reports for an app -### Initializing from private repositories +You can get a report about the app's git configuration using the `git:report` command: -> New as of 0.24.0 +```shell +dokku git:report +``` -Initializing from a private repository requires one of the following: +``` +=====> node-js-app git information + Git computed archive max files: 10000 + Git computed archive max size: 1073741824 + Git computed deploy branch: master + Git computed keep git dir: false + Git deploy branch: + Git global archive max files: + Git global archive max size: + Git global deploy branch: + Git global keep git dir: + Git keep git dir: + Git rev env var: GIT_REV + Git sha: a1b2c3d + Git source image: + Git last updated at: 1700000000 +``` -- A Public SSH Key (`id_rsa.pub` file) configured on the remote server, with the associated private key (`id_rsa`) in the Dokku server's `/home/dokku/.ssh/` directory. -- A configured [`.netrc`](https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html) entry. +The bare per-app keys (`deploy-branch`, `keep-git-dir`) and the `global-` keys hold the raw per-app or global value respectively, and are empty when nothing has been set. The `computed-` keys hold the effective value used at deploy time, falling back to the global value (where one has been set) and then to the built-in default. -Dokku provides the `git:auth` command which can be used to configure a `netrc` entry for the remote server. This command can be used to add or remove configuration for any remote server. +You can run the command for a specific app also. ```shell -# add credentials for github.com -dokku git:auth github.com username personal-access-token - -# remove credentials for github.com -dokku git:auth github.com +dokku git:report node-js-app ``` -For syncing to a private repository stored on a remote Git product such as GitHub or GitLab, Dokku's recommendation is to use a personal access token on a bot user where possible. Please see your service's documentation for information regarding the recommended best practices. +You can pass flags which will output only the value of the specific information you want. For example: -### Allowing remote repository hosts +```shell +dokku git:report node-js-app --git-deploy-branch +``` -By default, the Dokku host may not have access to a server containing the remote repository. This can be initialized via the `git:allow-host` command. +The `git:report` command also takes a `--format` flag, with the valid options including `stdout` (default) and `json`. The `json` output format can be used for automation purposes: ```shell -dokku git:allow-host github.com +dokku git:report node-js-app --format json ``` -Note that this command is currently not idempotent and may add duplicate entries to the `~dokku/.ssh/known_hosts` file. +The `--format` flag cannot be combined with an info flag. -### Verifying the cloning public key +## Properties -In order to clone a remote repository, the remote server should have the Dokku host's public key configured. This plugin does not currently create this key, but if there is one available, it can be shown via the `git:public-key` command. +### Settable properties -```shell -dokku git:public-key -``` +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `archive-max-files` | app + global | none | `--git-global-archive-max-files`, `--git-computed-archive-max-files` | Maximum number of files allowed in an uploaded archive deploy | +| `archive-max-size` | app + global | none | `--git-global-archive-max-size`, `--git-computed-archive-max-size` | Maximum total size of an uploaded archive deploy | +| `deploy-branch` | app + global | `master` | `--git-deploy-branch`, `--git-global-deploy-branch`, `--git-computed-deploy-branch` | Branch name pushed by the git remote that triggers a deploy | +| `keep-git-dir` | app + global | `false` | `--git-keep-git-dir`, `--git-global-keep-git-dir`, `--git-computed-keep-git-dir` | When `true`, retains the `.git` directory inside the build context | +| `rev-env-var` | app + global | `GIT_REV` | `--git-rev-env-var` | Environment variable name receiving the deployed commit SHA; empty disables injection | +| `source-image` | app + global | none | `--git-source-image` | Docker image to clone application source from when deploying from an image | -If there is no key, an error message is shown that displays the command that can be run on the Dokku server to generate a new public/private ssh key pair. +### Read-only flags + +The following flags surface in `git:report` but are not managed by `git:set` - they are derived from repository state: + +| Flag | Description | +|---|---| +| `--git-sha` | HEAD commit SHA of the app's git repo | +| `--git-last-updated-at` | UNIX timestamp of the last write to the deploy branch ref | diff --git a/docs/deployment/methods/image.md b/docs/deployment/methods/image.md new file mode 100644 index 00000000000..bc180a9f70a --- /dev/null +++ b/docs/deployment/methods/image.md @@ -0,0 +1,110 @@ +# Docker Image Deployment + +## Initializing an app repository from a docker image + +> [!IMPORTANT] +> New as of 0.24.0 + +A Dokku app repository can be initialized or updated from a Docker image via the `git:from-image` command. This command will either initialize the app repository or update it to include the specified Docker image via a `FROM` stanza. This is an excellent way of tracking changes when deploying only a given docker image, especially if deploying an image from a remote CI/CD pipeline. + +```shell +dokku git:from-image node-js-app my-registry/node-js-getting-started:latest +``` + +In the above example, Dokku will build the app as if the repository contained _only_ a `Dockerfile` with the following content: + +```Dockerfile +FROM my-registry/node-js-getting-started:latest +``` + +If the specified image already exists on the Dokku host, it will not be pulled again. To force a pull even when the image exists locally, use the `--force` flag: + +```shell +dokku git:from-image --force node-js-app my-registry/node-js-getting-started:latest +``` + +This is useful when the remote image has been updated but the tag remains the same. + +Triggering a build with the same arguments multiple times will result in Dokku exiting `0` early as there will be no changes detected. If the image tag is reused but the underlying image is different, it is recommended to use the image digest instead of the tag. This can be retrieved via the following command: + +```shell +# for images pushed to a remote registry +docker inspect --format='{{index .RepoDigests 0}}' $IMAGE_NAME + +# for images built locally and not pushed to a registry +# use when the previous command output is empty +docker images --no-trunc --quiet $IMAGE_NAME +``` + +The resulting `git:from-image` call would then be: + +```shell +# where the image sha is: sha256:9d187c3025d03c033dcc71e3a284fee53be88cc4c0356a19242758bc80cab673 +dokku git:from-image node-js-app my-registry/node-js-getting-started@sha256:9d187c3025d03c033dcc71e3a284fee53be88cc4c0356a19242758bc80cab673 +``` + +The `git:from-image` command can optionally take a git `user.name` and `user.email` argument (in that order) to customize the author. If the arguments are left empty, they will fallback to `Dokku` and `automated@dokku.sh`, respectively. + +```shell +dokku git:from-image node-js-app my-registry/node-js-getting-started:latest "Camila" "camila@example.com" +``` + +If the image is a private image that requires a docker login to access, the `registry:login` command should be used to log into the registry. See the [registry documentation](/docs/advanced-usage/registry-management.md#logging-into-a-registry) for more details on this process. + +Finally, certain images may require a custom build context in order for `ONBUILD ADD` and `ONBUILD COPY` statements to succeed. A custom build context can be specified via the `--build-dir` flag. All files in the specified `build-dir` will be copied into the repository for use within the `docker build` process. The build context _must_ be specified on each deploy, and is not otherwise persisted between builds. + +```shell +dokku git:from-image --build-dir path/to/build node-js-app domy-registrykku/node-js-getting-started:latest "Camila" "camila@example.com" +``` + +See the [dockerfile documentation](/docs/deployment/builders/dockerfiles.md) to learn about the different ways to configure Dockerfile-based deploys. + +## Initializing an app repository from a remote image without a registry + +> [!IMPORTANT] +> New as of 0.30.0 + +A Dokku app repository can be initialized or updated from the contents of an image archive tar file via the `git:load-image` command. This method can be used when a Docker Registry is unavailable to act as an intermediary for storing an image, such as when building an image in CI and deploying directly from that image. + +```shell +docker image save my-registry/node-js-getting-started:latest | ssh dokku@dokku.me git:load-image node-js-app my-registry/node-js-getting-started:latest +``` + +In the above example, we are saving the image to a tar file via `docker image save`, streaming that to the Dokku host, and then running `git:load-image` on the incoming stream. Dokku will build the app as if the repository contained _only_ a `Dockerfile` with the following content: + +```Dockerfile +FROM my-registry/node-js-getting-started:latest +``` + +When deploying an app via `git:load-image`, it is highly recommended to use a unique image tag when building the image. Not doing so will result in Dokku exiting `0` early as there will be no changes detected. If the image tag is reused but the underlying image is different, it is recommended to use the image digest instead of the tag. This can be retrieved via the following command: + +```shell + +# for images pushed to a remote registry +docker inspect --format='{{index .RepoDigests 0}}' $IMAGE_NAME + +# for images built locally and not pushed to a registry +# use when the previous command output is empty +docker images --no-trunc --quiet $IMAGE_NAME +``` + +The resulting `git:load-image` call would then be: + +```shell +# where the image sha is: sha256:9d187c3025d03c033dcc71e3a284fee53be88cc4c0356a19242758bc80cab673 +docker image save my-registry/node-js-getting-started:latest | ssh dokku@dokku.me git:load-image node-js-app my-registry/node-js-getting-started@sha256:9d187c3025d03c033dcc71e3a284fee53be88cc4c0356a19242758bc80cab673 +``` + +The `git:load-image` command can optionally take a git `user.name` and `user.email` argument (in that order) to customize the author. If the arguments are left empty, they will fallback to `Dokku` and `automated@dokku.sh`, respectively. + +```shell +docker image save my-registry/node-js-getting-started:latest | ssh dokku@dokku.me git:load-image node-js-app my-registry/node-js-getting-started:latest "Camila" "camila@example.com" +``` + +Finally, certain images may require a custom build context in order for `ONBUILD ADD` and `ONBUILD COPY` statements to succeed. A custom build context can be specified via the `--build-dir` flag. All files in the specified `build-dir` will be copied into the repository for use within the `docker build` process. The build context _must_ be specified on each deploy, and is not otherwise persisted between builds. + +```shell +docker image save my-registry/node-js-getting-started:latest | ssh dokku@dokku.me git:load-image --build-dir path/to/build node-js-app my-registry/node-js-getting-started:latest "Camila" "camila@example.com" +``` + +See the [dockerfile documentation](/docs/deployment/builders/dockerfiles.md) to learn about the different ways to configure Dockerfile-based deploys. diff --git a/docs/deployment/remote-commands.md b/docs/deployment/remote-commands.md index 0ad87480c91..283ea23640a 100644 --- a/docs/deployment/remote-commands.md +++ b/docs/deployment/remote-commands.md @@ -2,7 +2,7 @@ Dokku commands can be run over SSH. Anywhere you would run `dokku `, just run `ssh -t dokku@dokku.me ` The `-t` is used to request a pty. It is highly recommended to do so. -To avoid the need to type the `-t` option each time, simply create/modify a section in the `.ssh/config` on the client side, as follows: +To avoid the need to type the `-t` option each time, create/modify a section in the `.ssh/config` on the client side, as follows: ```ini Host dokku.me @@ -12,7 +12,7 @@ Host dokku.me ## Behavioral modifiers Dokku also supports certain command line arguments that augment its behavior. If using these over SSH, you must use the form `ssh -t dokku@dokku.me -- ` -in order to avoid SSH interpretting Dokku arguments for itself. +to avoid SSH interpreting Dokku arguments for itself. ``` --quiet suppress output headers @@ -71,7 +71,7 @@ All commands have the application name automatically set via the `--app` flag on The client supports several environment variables: -- `DOKKU_HOST` (default: `dokku` git remote): Used to interact with a specific remote server. Can be overriden via `--remote` flag. +- `DOKKU_HOST` (default: `dokku` git remote): Used to interact with a specific remote server. Can be overridden via the `--remote` flag. Must specify the `--app` flag when specified. - `DOKKU_PORT` (default: `22`): Used to specify a port to connect to the Dokku server on. It also supports several flags (all flags unspecified here are passed as is to the server): @@ -84,10 +84,32 @@ It also supports several flags (all flags unspecified here are passed as is to t In addition, the following commands have special local side-effects: - `apps:create`: - - If no local `--app` flag is specified or detected from a `dokku` git remote, a random name is generated and used for the app. - - The `dokku` git remote is set if not already set. + - If no local `--app` flag is specified or detected from a `dokku` git remote, a random name is generated and used for the app. + - The `dokku` git remote is set if not already set. - `apps:destroy`: - - Removes the local `dokku` git remote if set. + - Removes the local `dokku` git remote if set. + +#### Specifying a remote + +While many users will only ever push to a single dokku remote from a given repository, it may be useful to point the same repository at multiple remotes, especially in the case of pre-production environments. This can be done via the `remote` commands. These commands are *only* available within the official remote client, and are not server-side commands. + +To see your currently configured remote, run `dokku remote`. This will respect the `--remote` flag. + +To list all available remotes, run `dokku remote:list`. This is equivalent to running `git remote`. + +To add a remote, run `dokku remote:add $REMOTE_NAME dokku@dokku.me:$APP_NAME`, replacing `$REMOTE_NAME` with your desired remote name, the `$APP_NAME` with your desired app name and the host `dokku.me` with your Dokku host. This is equivalent to running `git remote add $REMOTE_NAME dokku@dokku.me:$APP_NAME`. + +The following remote formats are supported: + +- `dokku@HOSTNAME.TLD:APP_NAME` +- `ssh://dokku@HOSTNAME.TLD/APP_NAME` +- `ssh://dokku@HOSTNAME.TLD:HOST_PORT/APP_NAME` + +To remove a remote, run `dokku remote:remove $REMOTE_NAME`. This is equivalent to running `git remote remove $REMOTE_NAME`. + +By default, the remote in use is `dokku`. To change the remote the client uses by default, run `dokku remote:set $REMOTE_NAME`, where `$REMOTE_NAME` is the name of your desired remote. This will not check the value specified, so that remote *must* be available or errors will occur when running normal client commands. Note that specifying `--remote` when running dokku commands will override this configuration value. + +To clear the configured remote, run `dokku remote:unset`. The client will unset the configured remote, and will default back to `dokku`, or the value of `--remote` if specified. ## Unofficial Clients diff --git a/docs/deployment/schedulers/docker-local.md b/docs/deployment/schedulers/docker-local.md index 5a8188c8eb7..089db711316 100644 --- a/docs/deployment/schedulers/docker-local.md +++ b/docs/deployment/schedulers/docker-local.md @@ -1,44 +1,68 @@ # Docker Local Scheduler +> [!IMPORTANT] > Subcommands new as of 0.12.12 ``` scheduler-docker-local:report [] [] # Displays a scheduler-docker-local report for one or more apps -scheduler-docker-local:set () # Set or clear a scheduler-docker-local property for an app +scheduler-docker-local:set [|--global] () # Set or clear a scheduler-docker-local property for an app or globally ``` +> [!IMPORTANT] > New as of 0.12.0 Dokku natively includes functionality to manage application lifecycles for a single server using the `scheduler-docker-local` plugin. It is the default scheduler, but as with all schedulers, it is set on a per-application basis. The scheduler can currently be overridden by running the following command: ```shell -dokku config:set node-js-app DOCKER_SCHEDULER=docker-local +dokku scheduler:set node-js-app selected docker-local ``` -As it is the default, unsetting the `DOCKER_SCHEDULER` config variable is also a valid way to reset the scheduler. +As it is the default, unsetting the `selected` scheduler property is also a valid way to reset the scheduler. ```shell -dokku config:unset node-js-app DOCKER_SCHEDULER +dokku scheduler:set node-js-app selected ``` ## Usage -### Disabling chown of persistent storage +### Deploying amd64 images on arm64 -The `scheduler-docker-local` plugin will ensure your storage mounts are owned by either `herokuishuser` or the overridden value you have set in `DOKKU_APP_USER`. You may disable this by running the following `scheduler-docker-local:set` command for your application: +> [!IMPORTANT] +> New as of 0.33.0 + +Many builders only produce amd64-compatible images. The docker-local scheduler will automatically detect these and run them via the `--platform=linux/amd64` on arm64 deploy targets. + +### Disabling the init process + +The `scheduler-docker-local` injects an init process by default via the `--init`. For some apps - such as those where the built docker image uses S6 as the init - this may be undesirable and cause issues with process starts. You may disable this by running the following `scheduler-docker-local:set` command for your application: + +```shell +dokku scheduler-docker-local:set node-js-app init-process false +``` + +Once set, you may re-enable it by setting a blank value for `init-process`: ```shell -dokku scheduler-docker-local:set node-js-app disable-chown true +dokku scheduler-docker-local:set node-js-app init-process ``` -Once set, you may re-enable it by setting a blank value for `disable-chown`: +The default value may also be configured globally with the `--global` flag. Per-app values take precedence over the global value when set. ```shell -dokku scheduler-docker-local:set node-js-app disable-chown +dokku scheduler-docker-local:set --global init-process false ``` +The global value may also be unset by setting a blank value. + +```shell +dokku scheduler-docker-local:set --global init-process +``` + +All image containers with the label `org.opencontainers.image.vendor=linuxserver.io` will have the automatic init process injection force-disabled without further intervention when neither an app-level nor a global value is set. + ### Deploying Process Types in Parallel +> [!IMPORTANT] > New as of 0.25.5 By default, Dokku deploys an app's processes one-by-one in order, with the `web` process being deployed first. Deployment parallelism may be achieved by setting the `parallel-schedule-count` property, which defaults to `1`. Increasing this number increases the number of process types that may be deployed in parallel (with the web process being the exception). @@ -54,6 +78,18 @@ Once set, you may reset it by setting a blank value for `parallel-schedule-count dokku scheduler-docker-local:set node-js-app parallel-schedule-count ``` +The default value may also be configured globally with the `--global` flag. Per-app values take precedence over the global value when set. + +```shell +dokku scheduler-docker-local:set --global parallel-schedule-count 4 +``` + +The global value may also be unset by setting a blank value. + +```shell +dokku scheduler-docker-local:set --global parallel-schedule-count +``` + If the value of `parallel-schedule-count` is increased and a given process type fails to schedule successfully, then any in-flight process types will continue to be processed, while all process types that have not been scheduled will be skipped before the deployment finally fails. Container scheduling output is shown in the order it is received, and thus may be out of order in case of output to stderr. @@ -62,19 +98,14 @@ Note that increasing the value of `parallel-schedule-count` may significantly im #### Increasing parallelism within a process deploy +> [!IMPORTANT] > New as of 0.26.0 By default, Dokku will deploy one instance of a given process type at a time. This can be increased by customizing the `app.json` `formation` key to include a `max_parallel` key for the given process type. -An `app.json` file can be committed to the root of the pushed app repository, and must be within the built image artifact in the image's working directory as shown below. - -- Buildpacks: `/app/app.json` -- Dockerfile: `WORKDIR/app.json` or `/app.json` (if no working directory specified) -- Docker Image: `WORKDIR/app.json` or `/app.json` (if no working directory specified) - The `formation` key should be specified as follows in the `app.json` file: -```Procfile +```json { "formation": { "web": { @@ -87,11 +118,68 @@ The `formation` key should be specified as follows in the `app.json` file: } ``` -Omitting or removing the entry will result in parallelism for that process type to return to 1 entry at a time. This can be combined with the `parallel-schedule-count` property to speed up deployments. +Omitting or removing the entry will result in parallelism for that process type to return to 1 entry at a time. This can be combined with the `parallel-schedule-count` property to speed up deployments. Note that increasing the value of `max_parallel` may significantly impact CPU utilization on your host as your app containers - and their respective processes - start up. Setting a value higher than the number of available CPUs is discouraged. It is recommended that users carefully set this value so as not to overburden their server. -## Implemented Triggers +See the [app.json location documentation](/docs/advanced-usage/deployment-tasks.md#changing-the-appjson-location) for more information on where to place your `app.json` file. + +### Displaying scheduler-docker-local reports for an app + +You can get a report about the app's scheduler-docker-local configuration using the `scheduler-docker-local:report` command: + +```shell +dokku scheduler-docker-local:report +``` + +``` +=====> node-js-app scheduler-docker-local information + Scheduler docker local computed init process: true + Scheduler docker local computed parallel schedule count:1 + Scheduler docker local global init process: true + Scheduler docker local global parallel schedule count: 1 + Scheduler docker local init process: + Scheduler docker local parallel schedule count: +``` + +You can run the command for a specific app also. + +```shell +dokku scheduler-docker-local:report node-js-app +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku scheduler-docker-local:report node-js-app --scheduler-docker-local-computed-init-process +``` + +When run against `--global`, only the global keys are reported. + +```shell +dokku scheduler-docker-local:report --global +``` + +The available keys are: + +- `--scheduler-docker-local-init-process`: the raw per-app init-process value (empty when unset). +- `--scheduler-docker-local-computed-init-process`: the effective init-process value, computed as the per-app value if set, otherwise the global value, otherwise `true`. +- `--scheduler-docker-local-global-init-process`: the global init-process value (defaults to `true`). +- `--scheduler-docker-local-parallel-schedule-count`: the raw per-app parallel-schedule-count value (empty when unset). +- `--scheduler-docker-local-computed-parallel-schedule-count`: the effective parallel-schedule-count value, computed as the per-app value if set, otherwise the global value, otherwise `1`. +- `--scheduler-docker-local-global-parallel-schedule-count`: the global parallel-schedule-count value (defaults to `1`). + +The report may also be emitted as JSON using `--format json`, which is useful for external tooling that needs to distinguish a value set on the app from one that is defaulting. + +```shell +dokku scheduler-docker-local:report node-js-app --format json +``` + +## Scheduler Interface + +The following sections describe implemented scheduler functionality for the `docker-local` scheduler. + +### Implemented Commands and Triggers This plugin implements various functionality through `plugn` triggers to integrate with Docker for running apps on a single server. The following functionality is supported by the `scheduler-docker-local` plugin. @@ -105,22 +193,35 @@ This plugin implements various functionality through `plugn` triggers to integra - `ps:stop` - `run` -## Supported Resource Management Properties +### Logging support + +App logs for the `logs` command are fetched from running containers via the `docker` cli. To persist logs across deployments, consider using Dokku's [vector integration](/docs/deployment/logs.md#vector-logging-shipping) to ship logs to another service or a third-party platform. + +### Supported Resource Management Properties The `docker-local` scheduler supports a minimal list of resource _limits_ and _reservations_. The following properties are supported: -### Resource Limits +#### Resource Limits - cpu: (docker option: `--cpus`), is specified in number of CPUs a process can access. - - See the ["CPU" section](https://docs.docker.com/config/containers/resource_constraints/#cpu) of the Docker Runtime Options documentation for more information. -- memory: (docker option: `--memory`) should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes). - - See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information. + - See the ["CPU" section](https://docs.docker.com/config/containers/resource_constraints/#cpu) of the Docker Runtime Options documentation for more information. +- memory: (docker option: `--memory`) should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes). Default unit is `m` (megabytes). + - See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information. - memory-swap: (docker option: `--memory-swap`) should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes) - - See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information. + - See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information. - nvidia-gpus: (docker option: `--gpus`), is specified in number of Nvidia GPUs a process can access. - - See the ["GPU" section](https://docs.docker.com/config/containers/resource_constraints/#gpu) of the Docker Runtime Options documentation for more information. + - See the ["GPU" section](https://docs.docker.com/config/containers/resource_constraints/#gpu) of the Docker Runtime Options documentation for more information. + +#### Resource Reservations + +- memory: (docker option: `--memory-reservation`) should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes). Default unit is `m` (megabytes). + - See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information. + +## Properties -### Resource Reservations +### Settable properties -- memory: (docker option: `--memory-reservation`) should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes) - - See the ["Memory" section](https://docs.docker.com/config/containers/resource_constraints/#memory) of the Docker Runtime Options documentation for more information. +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `init-process` | app + global | `true` | `--scheduler-docker-local-init-process`, `--scheduler-docker-local-global-init-process`, `--scheduler-docker-local-computed-init-process` | When `true`, runs containers with Docker's `--init` flag (reaping zombie processes) | +| `parallel-schedule-count` | app + global | `1` | `--scheduler-docker-local-parallel-schedule-count`, `--scheduler-docker-local-global-parallel-schedule-count`, `--scheduler-docker-local-computed-parallel-schedule-count` | Maximum number of containers scheduled in parallel during a deploy | diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md new file mode 100644 index 00000000000..fd55e2adb72 --- /dev/null +++ b/docs/deployment/schedulers/k3s.md @@ -0,0 +1,949 @@ +# K3s Scheduler + +> [!IMPORTANT] +> New as of 0.33.0 + +``` +scheduler-k3s:annotations:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear an annotation for a given app/process-type/resource-type combination +scheduler-k3s:annotations:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Displays a scheduler-k3s annotations report for one or more apps +scheduler-k3s:autoscaling-auth:set [<--metadata key=value>...], Set or clear a scheduler-k3s autoscaling keda trigger authentication resource for an app +scheduler-k3s:autoscaling-auth:report [|--global] [--format stdout|json] [--include-metadata] # Displays a scheduler-k3s autoscaling auth report for one or more apps +scheduler-k3s:charts:report [] [--format stdout|json] # Displays a scheduler-k3s chart override report +scheduler-k3s:charts:set () # Set or clear a chart-specific helm value +scheduler-k3s:cluster:add [ssh://user@host:port] # Adds a server node to a Dokku-managed cluster +scheduler-k3s:cluster:list # Lists all nodes in a Dokku-managed cluster +scheduler-k3s:cluster:remove [node-id] # Removes client node to a Dokku-managed cluster +scheduler-k3s:ensure-charts # Ensures the k3s charts are installed +scheduler-k3s:initialize # Initializes a cluster +scheduler-k3s:labels:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE> # Set or clear a label for a given app/process-type/resource-type combination +scheduler-k3s:labels:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Displays a scheduler-k3s labels report for one or more apps +scheduler-k3s:preview [--context N] [--show-secrets] [--show-secrets-decoded] # Displays a diff between the current and next deployment for an app +scheduler-k3s:profiles:add [--role ROLE] [--insecure-allow-unknown-hosts] [--taint-scheduling] [--kubelet-args KUBELET_ARGS] Adds a node profile to the k3s cluster +scheduler-k3s:profiles:list [--format json|stdout] # Lists all node profiles in the k3s cluster +scheduler-k3s:profiles:remove # Removes a node profile from the k3s cluster +scheduler-k3s:report [] [] # Displays a scheduler-k3s report for one or more apps +scheduler-k3s:set [|--global] () # Set or clear a scheduler-k3s property for an app or the scheduler +scheduler-k3s:show-kubeconfig # Displays the kubeconfig for remote usage +scheduler-k3s:uninstall # Uninstalls k3s from the Dokku server +``` + +> [!NOTE] +> The k3s plugin replaces the external [scheduler-kubernetes](https://github.com/dokku/dokku-scheduler-kubernetes) plugin. Users can continue to use the external plugin as necessary, but all future development will occur on the official core k3s plugin. + +For multi-server support, Dokku provides the ability for users to setup a K3s cluster. As with all schedulers, it is set on a per-app basis. The scheduler can currently be overridden by running the following command: + +```shell +dokku scheduler:set node-js-app selected k3s +``` + +As it is the default, unsetting the `selected` scheduler property is also a valid way to reset the scheduler. + +```shell +dokku scheduler:set node-js-app k3s +``` + +## Usage + +> [!IMPORTANT] +> The k3s plugin requires usage of a docker registry to store deployed image artifacts. See the [registry documentation](/docs/advanced-usage/registry-management.md) for more details on how to configure a registry. + +### Initializing a cluster + +> [!WARNING] +> This command must be run as root + +Clusters can be initialized via the `scheduler-k3s:initialize` command. This will start a k3s cluster on the Dokku node itself. + +```shell +dokku scheduler-k3s:initialize +``` + +By default, the k3s installation can run both app and system workloads. For clusters where app workloads are run on distinct worker nodes, initialize the cluster with the `--taint-scheduling` flag, which will allow _only_ Critical cluster components on the k3s control-plane nodes. + +```shell +dokku scheduler-k3s:initialize --taint-scheduling +``` + +By default, Dokku will attempt to auto-detect the IP address of the server. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag: + +```shell +dokku scheduler-k3s:initialize --server-ip 192.168.20.15 +``` + +Dokku's k3s integration natively uses `nginx` as it's ingress load balancer via [ingress-nginx](https://github.com/kubernetes/ingress-nginx). Properties set by the `nginx` plugin will be respected, either by turning them into annotations or creating a custom server/location snippet that the `ingress-nginx` project can use. A `ps:restart` is required after changing nginx properties in order to have them apply to running resources. + +Dokku can also use Traefik on cluster initialization via the [Traefik's CRDs](https://doc.traefik.io/traefik/providers/kubernetes-crd/). To change the ingress, set the `--ingress-class` flag: + +```shell +dokku scheduler-k3s:initialize --ingress-class traefik +``` + +### Adding nodes to the cluster + +> [!WARNING] +> The `dokku` user _must_ be able to ssh onto the server in order to connect nodes to the cluster. The remote user must be root or have sudo enabled, or the install will fail. + +#### Adding a worker node + +Nodes that run app workloads can be added via the `scheduler-k3s:cluster:add` command. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in worker mode. Workers are typically used to run app workloads. + +```shell +dokku scheduler-k3s:cluster:add ssh://root@worker-1.example.com +``` + +Per-node kubelet flags can be supplied by passing `--kubelet-args` with a comma-separated `key=value` list. This is useful for tuning scheduler capacity or enforcing cluster-wide defaults at the node level. + +```shell +dokku scheduler-k3s:cluster:add \ + --kubelet-args allowed-unsafe-sysctls=net.ipv6.conf.all.disable_ipv6 \ + ssh://root@worker-1.example.com +``` + +Multiple kubelet arguments can be specified in the same call by separating them with commas. The following example enables IPv4 forwarding while also increasing the pod density on the worker. + +```shell +dokku scheduler-k3s:cluster:add \ + --kubelet-args allowed-unsafe-sysctls=net.ipv6.conf.all.disable_ipv6,max-pods=150 \ + ssh://root@worker-2.example.com +``` + +If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag: + +```shell +dokku scheduler-k3s:cluster:add --insecure-allow-unknown-hosts ssh://root@worker-1.example.com +``` + +By default, Dokku will attempt to auto-detect the IP address of the Dokku server for the remote server to connect to. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag: + +```shell +dokku scheduler-k3s:cluster:add --server-ip 192.168.20.15 ssh://root@worker-1.example.com +``` + +#### Adding a server node + +> [!NOTE] +> Only the initial Dokku server will be properly configured for push deployment, and should be considered your git remote. Additional server nodes are for ensuring high-availability of the K3s etcd state. Ensure this server is properly backed up and restorable or deployments will not work. + +Server nodes are typically used to replicate the cluster state, and it is recommended to have an odd number of nodes spread across several availability zones (datacenters in close proximity within a region). This allows for higher availability in the event of a cluster failure. Server nodes run control-plane services such as the traefik load balancer and the etcd backing store. + +Server nodes can also be added with the `scheduler-k3s:cluster:add` command by specifying `--role server`. This will ssh onto the specified server, install k3s, and join it to the current Dokku node in server mode. + +```shell +dokku scheduler-k3s:cluster:add --role server ssh://root@server-1.example.com +``` + +Server nodes allow any workloads to be scheduled on them by default, in addition to the control-plane, etcd, and the scheduler itself. To avoid app workloads being scheduled on your control-plane, use the `--taint-scheduling` flag: + +```shell +dokku scheduler-k3s:cluster:add --role server --taint-scheduling ssh://root@server-1.example.com +``` + +If the server isn't in the `known_hosts` file, the connection will fail. This can be bypassed by setting the `--insecure-allow-unknown-hosts` flag: + +```shell +dokku scheduler-k3s:cluster:add --role server --insecure-allow-unknown-hosts ssh://root@server-1.example.com +``` + +By default, Dokku will attempt to auto-detect the IP address of the Dokku server for the remote server to connect to. In cases where the auto-detected IP address is incorrect, an override may be specified via the `--server-ip` flag: + +```shell +dokku scheduler-k3s:cluster:add --role server --server-ip 192.168.20.15 ssh://root@server-1.example.com +``` + +#### Changing the network interface + +When attaching an worker or server node, the K3s plugin will look at the IP associated with the `eth0` interface and use that to connect the new node to the cluster. To change this, set the `network-interface` property to the appropriate value. + +```shell +dokku scheduler-k3s:set --global network-interface eth1 +``` + +### Node Profiles + +Node profiles capture repeatable `scheduler-k3s:cluster:add` options so you can join multiple nodes with identical settings. A profile name can be specified for the `scheduler-k3s:cluster:add` command via the `--profile ` flag. Any flags passed directly to `scheduler-k3s:cluster:add` override the stored values for that run. + +#### Listing profiles + +Display stored profiles to understand which roles and behaviors will be used. + +```shell +dokku scheduler-k3s:profiles:list +``` + +``` +name role +awesome-profile worker +``` + +This command also takes an optional `--format` flag to specify a format for the output. Options include `json` and `stdout` + +#### Adding profiles + +Create or update a profile that defines how new nodes should be prepared before joining the cluster. + +```shell +dokku scheduler-k3s:profiles:add edge-workers \ + --role worker \ + --insecure-allow-unknown-hosts \ + --kubelet-args protect-kernel-defaults=true,eviction-hard=memory.available<200Mi +``` + +Profile names must be alphanumeric, may include internal dashes, cannot start/end with a dash, and must be ≤32 characters. Other than the `--server-ip` flag, all flags used for `scheduler-k3s:cluster:add` are valid for the `scheduler-k3s:profiles:add` command. + +#### scheduler-k3s:profiles:remove + +Delete a profile once it’s no longer required. + +```shell +dokku scheduler-k3s:profiles:remove edge-workers +``` + +Removal only deletes the stored definition; nodes that already joined the cluster keep their existing configuration. + +### Changing deployment settings + +The k3s plugin provides a number of settings that can be used to managed deployments on a per-app basis. The following table outlines ones not covered elsewhere: + +| Name | Description | Global Default | +|-----------------------|---------------------------------------------------|--------------------| +| `deploy-timeout` | Controls when app deploys will timeout in seconds | `300s` | +| `kustomize-root-path` | Controls the folder context from the deployed repository used for Kustomize | `config/kustomize` | +| `image-pull-secrets` | Name of a kubernetes secret used to auth against a registry | Contents of `~/.docker/config.json` from Dokku server | +| `namespace` | Controls the namespace used for resource creation | `default` | +| `rollback-on-failure` | Whether to rollback failed deploys | `false` | +| `shm-size` | Default shared memory size for pods | Kubernetes default | + +All settings can be set via the `scheduler-k3s:set` command. Using `deploy-timeout` as an example: + +```shell +dokku scheduler-k3s:set node-js-app deploy-timeout 60s +``` + +The default value may be set by passing an empty value for the option in question: + +```shell +dokku scheduler-k3s:set node-js-app deploy-timeout +``` + +Properties can also be set globally. If not set for an app, the global value will apply. + +```shell +dokku scheduler-k3s:set --global deploy-timeout 60s +``` + +The global default value may be set by passing an empty value for the option. + +```shell +dokku scheduler-k3s:set --global deploy-timeout +``` + +### Previewing deployment changes + +Before triggering a deploy, the `scheduler-k3s:preview` command can be used to display a unified diff between the manifests currently stored in the live Helm release for an app and the manifests that the next deploy would roll out: + +```shell +dokku scheduler-k3s:preview node-js-app +``` + +A clean redeploy with no property changes produces no output. After modifying a property that affects the main app chart (for example `dokku resource:limit --memory 256m node-js-app`), the command shows the unified diff for each changed resource. For an app that has never been deployed, the entire proposed manifest is rendered as added lines. + +By default each change is shown with 3 lines of surrounding context, git-diff style. The `--context` flag overrides this; pass `-1` to render the full resource around every change: + +```shell +dokku scheduler-k3s:preview node-js-app --context 0 +dokku scheduler-k3s:preview node-js-app --context -1 +``` + +By default any `kind: Secret` resources have their `data` values redacted so secret bytes never appear in terminal scrollback or CI logs. Two flags adjust this behavior: + +```shell +dokku scheduler-k3s:preview node-js-app --show-secrets +dokku scheduler-k3s:preview node-js-app --show-secrets-decoded +``` + +`--show-secrets` keeps the raw base64-encoded values, and `--show-secrets-decoded` renders them base64-decoded. In current dokku, the only Secret objects in the main app chart come from KEDA autoscaling auth resources configured via [`scheduler-k3s:autoscaling-auth:set`](#workload-autoscaling-authentication); for apps without autoscaling auth, these flags are no-ops. + +> [!IMPORTANT] +> The preview only covers the main app helm release (the release named after the app). Dokku installs three auxiliary helm releases per app for config-var storage, image-pull credentials, and TLS certificates, and changes to those releases do **not** appear in this preview. In particular, a `dokku config:set` change followed by `scheduler-k3s:preview` will show no diff because the new config-var value lives in the separate `config-` helm release. + +> [!NOTE] +> Helm renders the proposed manifest using a client-side dry-run. The `lookup` template function returns empty values in this mode, so charts overridden via `scheduler-k3s:charts:set` that depend on `lookup` may render differently here than at actual deploy time. Dokku's bundled chart templates do not use `lookup`. + +### Exposing services on the network + +Dokku will automatically expose the `web` process as a Kubernetes Service, with all others being treated as background processes. In some cases, it may be useful to have other processes exposed as Kubernetes Service objects so as to segregate internal http endpoints from public http endpoints. This can be done by modifying the `app.json` Formation entry for your process type. + +```json +{ + "formation": { + "internal-web": { + "service": { + "exposed": true + } + } + } +} +``` + +In the above example, the `internal-web` process is exposed as a service. The `PORT` variable for the process will be set to `5000`, and a kubernetes `Service` object will be created pointing at your processes. + +> [!NOTE] +> It is not possible to modify the port mapping, nor is it possible to assign domains or SSL to a non-web process. + +### SSL Certificates + +#### Enabling letsencrypt integration + +By default, letsencrypt is disabled and https port mappings are ignored. To enable, set the `letsencrypt-email-prod` or `letsencrypt-email-stag` property with the `--global` flag: + +```shell +# set the value for prod +dokku scheduler-k3s:set --global letsencrypt-email-prod automated@dokku.sh + +# set the value for stag +dokku scheduler-k3s:set --global letsencrypt-email-stag automated@dokku.sh +``` + +After enabling and rebuilding, all apps with an `http:80` port mapping will have a corresponding `https:443` added and ssl will be automatically enabled. All http requests will then be redirected to https. + +#### Customizing the letsencrypt email per app + +The `letsencrypt-email-prod` and `letsencrypt-email-stag` properties can also be set per app, overriding the global value for that app. This is useful when different apps should register their certificates under different contact emails. + +```shell +dokku scheduler-k3s:set node-js-app letsencrypt-email-prod team@node-js-app.com +``` + +The value resolves in two steps: the app's `letsencrypt-server` selects which server (`prod` or `staging`) is used, and the matching `letsencrypt-email-` property is then resolved as the app-level value, falling back to the global value. Because the two emails are per-server, an app-level `letsencrypt-email-stag` only takes effect once the app's `letsencrypt-server` is set to `staging`. + +When an app sets its own email for the selected server, Dokku renders a namespaced cert-manager `Issuer` into the app's own release using that email instead of pointing the app at the shared global `ClusterIssuer`. Apps without an app-level email continue to use the shared `ClusterIssuer` with the global email. + +The default value may be set by passing an empty value for the option, which falls the app back to the global value: + +```shell +dokku scheduler-k3s:set node-js-app letsencrypt-email-prod +``` + +The computed value in effect for an app can be inspected via the report command: + +```shell +dokku scheduler-k3s:report node-js-app --scheduler-k3s-computed-letsencrypt-email-prod +``` + +#### Customizing the letsencrypt server + +The letsencrypt integration is set to the production letsencrypt server by default. This can be changed on an app-level by setting the `letsencrypt-server` property with the `scheduler-k3s:set` command + +```shell +dokku scheduler-k3s:set node-js-app letsencrypt-server staging +``` + +The default value may be set by passing an empty value for the option: + +```shell +dokku scheduler-k3s:set node-js-app letsencrypt-server +``` + +The `letsencrypt-server` property can also be set globally. The global default is `production`. + +```shell +dokku scheduler-k3s:set --global letsencrypt-server staging +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku scheduler-k3s:set --global letsencrypt-server staging +``` + +Letsencrypt can be completely disabled for a given app by setting the `letsencrypt-server` to `false` + +```shell +dokku scheduler-k3s:set node-js-app letsencrypt-server false +``` + +The server can also be disabled globally, and then conditionally enabled on a per-app basis: + +```shell +dokku scheduler-k3s:set --global letsencrypt-server false +``` + +#### Using imported SSL certificates + +SSL certificates imported via the `certs` plugin can be used with the k3s scheduler. When a certificate is imported, it is automatically synced to Kubernetes as a TLS secret and will be used for the app's ingress configuration. + +To import a certificate: + +```shell +dokku certs:add node-js-app server.crt server.key +``` + +When a certificate is imported: + +- A Kubernetes TLS secret named `tls-` is created in the app's namespace +- The ingress configuration is updated to use the imported certificate +- Automatic Let's Encrypt certificate generation is disabled for the app + +Imported certificates take precedence over Let's Encrypt certificates. If you have both an imported certificate and Let's Encrypt configured, the imported certificate will be used. + +To remove an imported certificate: + +```shell +dokku certs:remove node-js-app +``` + +When a certificate is removed: + +- The TLS secret is deleted from Kubernetes +- The app is automatically redeployed to update the ingress configuration +- If Let's Encrypt is configured, automatic certificate generation will resume + +When an app is destroyed, any associated TLS secret is automatically cleaned up. + +### Customizing Annotations and Labels + +> [!NOTE] +> The cron ID is used as the process type if your app deploys any cron tasks + +#### Setting Annotations + +Dokku injects certain annotations into each created resource by default, but it may be necessary to inject others for tighter integration with third-party tools. The `scheduler-k3s:annotations:set` command can be used to perform this task. The command takes an app name and a required `--resource-type` flag. + +```shell +dokku scheduler-k3s:annotations:set node-js-app annotation.key annotation.value --resource-type deployment +``` + +If not specified, the annotation will be applied to all processes within an app, though it may be further scoped to a specific process type via the `--process-type` flag. + +```shell +dokku scheduler-k3s:annotations:set node-js-app annotation.key annotation.value --resource-type deployment --process-type web +``` + +The following resource types are supported: + +- `certificate` +- `cronjob` +- `deployment` +- `ingress` +- `job` +- `pod` +- `secret` +- `service` +- `serviceaccount` +- `traefik_ingressroute` +- `traefik_middleware` + +Annotation keys may contain `/` (e.g. Kubernetes-style keys such as `prometheus.io/scrape`) and values may span multiple lines; both are preserved verbatim. + +A `ps:restart` is required after setting annotations in order to have them apply to running resources. + +#### Removing an annotation + +To unset an annotation, pass an empty value: + +```shell +dokku scheduler-k3s:annotations:set node-js-app annotation.key --resource-type deployment +dokku scheduler-k3s:annotations:set node-js-app annotation.key --resource-type deployment --process-type web +``` + +A `ps:restart` is required after removing annotations in order to remove them from running resources. + +#### Displaying annotations + +Configured annotations can be inspected with the `scheduler-k3s:annotations:report` command. Without arguments, it iterates every app and prints all annotations. Passing an app name (or `--global`) scopes the report: + +```shell +dokku scheduler-k3s:annotations:report +dokku scheduler-k3s:annotations:report node-js-app +dokku scheduler-k3s:annotations:report --global +``` + +`--process-type` and `--resource-type` flags narrow the output to a specific scope, matching the flags accepted by `scheduler-k3s:annotations:set`: + +```shell +dokku scheduler-k3s:annotations:report node-js-app --resource-type deployment +dokku scheduler-k3s:annotations:report node-js-app --process-type web --resource-type deployment +``` + +JSON output emits flat keys of the form `{process_type}.{resource_type}.{annotation_key}`. The literal `--global` process type is rendered as `global` to keep keys free of leading dashes: + +```shell +dokku scheduler-k3s:annotations:report node-js-app --format json +``` + +A single value can also be read directly with a flag of the form `--scheduler-k3s-annotations.{process_type}.{resource_type}.{annotation_key}`: + +```shell +dokku scheduler-k3s:annotations:report node-js-app --scheduler-k3s-annotations.global.deployment.annotation.key +``` + +#### Setting Labels + +Dokku injects certain labels into each created resource by default, but it may be necessary to inject others for tighter integration with third-party tools. The `scheduler-k3s:labels:set` command can be used to perform this task. The command takes an app name and a required `--resource-type` flag. + +```shell +dokku scheduler-k3s:labels:set node-js-app label.key label.value --resource-type deployment +``` + +If not specified, the label will be applied to all processes within an app, though it may be further scoped to a specific process type via the `--process-type` flag. + +```shell +dokku scheduler-k3s:labels:set node-js-app label.key label.value --resource-type deployment --process-type web +``` + +The following resource types are supported: + +- `certificate` +- `cronjob` +- `deployment` +- `ingress` +- `job` +- `pod` +- `secret` +- `service` +- `serviceaccount` +- `traefik_ingressroute` +- `traefik_middleware` + +Label keys may contain `/` (e.g. Kubernetes-style keys such as `app.kubernetes.io/part-of`) and values may span multiple lines; both are preserved verbatim. + +A `ps:restart` is required after setting labels in order to have them apply to running resources. + +#### Removing a label + +To unset an label, pass an empty value: + +```shell +dokku scheduler-k3s:annotations:set node-js-app label.key --resource-type deployment +dokku scheduler-k3s:labels:set node-js-app label.key --resource-type deployment --process-type web +``` + +A `ps:restart` is required after removing labels in order to remove them from running resources. + +#### Displaying labels + +Configured labels can be inspected with the `scheduler-k3s:labels:report` command. The surface mirrors `scheduler-k3s:annotations:report`: + +```shell +dokku scheduler-k3s:labels:report +dokku scheduler-k3s:labels:report node-js-app +dokku scheduler-k3s:labels:report --global +dokku scheduler-k3s:labels:report node-js-app --resource-type deployment +dokku scheduler-k3s:labels:report node-js-app --process-type web --resource-type deployment +dokku scheduler-k3s:labels:report node-js-app --format json +dokku scheduler-k3s:labels:report node-js-app --scheduler-k3s-labels.global.deployment.label.key +``` + +### Autoscaling + +#### Workload Autoscaling + +> [!IMPORTANT] +> New as of 0.33.8 +> Users with older installations will need to manually install Keda. + +Autoscaling in k3s is managed by [Keda](https://keda.sh/), which integrates with a variety of external metric providers to allow for autoscaling application workloads. + +To enable autoscaling, use the `app.json` `formation.$PROCESS_TYPE.autoscaling` key to manage rules. In addition to the existing configuration used for process management, each process type in the `formation.$PROCESS_TYPE.autoscaling` key can have the following keys: + +- `min_quantity`: The minimum number of instances the application can run. If not specified, the `quantity` specified for the app is used. +- `max_quantity`: The maximum number of instances the application can run. If not specified, the higher value of `quantity` and the `min_quantity` is used. +- `polling_interval_seconds`: (default: 30) The interval to wait for polling each of the configured triggers +- `cooldown_seconds`: (default: 300) The number of seconds to wait in between each scaling event +- `triggers`: A list of autoscaling triggers. + +Autoscaling triggers are passed as is to Keda, and should match the configuration keda uses for a given [scaler](https://keda.sh/docs/2.19/scalers/). Below is an example for [datadog](https://keda.sh/docs/2.19/scalers/datadog/#example-2---driving-scale-directly): + +```json +{ + "formation": { + "web": { + "autoscaling": { + "min_quantity": 1, + "max_quantity": 10, + "triggers": [ + { + "name": "name-for-trigger", + "type": "datadog", + "metadata": { + "query": "per_second(sum:http.requests{service:myservice1}).rollup(max, 300))/180,per_second(sum:http.backlog{service:myservice1}).rollup(max, 300)/30", + "queryValue": "1", + "queryAggregator": "max" + } + } + ] + } + } + } +} +``` + +Each value in the `metadata` stanza can use the following interpolated strings: + +- `DOKKU_DEPLOYMENT_NAME`: The name of the deployment being scaled +- `DOKKU_PROCESS_TYPE`: The name of the process being scaled +- `DOKKU_APP_NAME`: The name of the app being scaled + +Dokku configures a Keda `fallback` (with a failure threshold of `3` and the configured replica count) on the generated `ScaledObject` so that scaling falls back to the configured replica count when a scaler's metric source becomes unavailable. Keda only allows `fallback` when at least one trigger is something other than `cpu` or `memory`, so Dokku omits the `fallback` block when every configured trigger is a `cpu` or `memory` scaler. + +##### HTTP Autoscaling + +In addition to the built-in scalers that Keda provides, Dokku also supports Keda's HTTP Add On. This requires that the addon be properly installed and configured. For existing k3s clusters, this can be performed by the `scheduler-k3s:ensure-charts` command: + +```shell +dokku scheduler-k3s:ensure-charts +``` + +> [!NOTE] +> Users who wish to use this functionality on a cluster not managed by Dokku will need to manually install the `keda-http-add-on` into the `keda` namespace. Please consult the `keda-http-add-on` [install documentation](https://kedacore.github.io/http-add-on/install.html) for further details. + +> [!WARNING] +> If the `keda-http-add-on` chart is not installed, then this trigger will be ignored. + +Once the chart is configured, an `http` trigger can be specified like so: + +```json +{ + "formation": { + "web": { + "autoscaling": { + "min_quantity": 1, + "max_quantity": 10, + "triggers": [ + { + "type": "http", + "metadata": { + "scaledown_period_seconds": "150", + "request_rate_target_value": "50" + } + } + ] + } + } + } +} +``` + +The following metadata properties are supported with the http autoscaler: + +- `scale_by`: (default: `request_rate`) whether to scale by `concurrency` or `request_rate`. +- `scaledown_period_seconds`: (default: `300`) period to wait after the last reported active before scaling the resource back to 0. +- `request_rate_granularity_seconds`: (default: `1`) granualarity of the aggregated requests for the request rate calculation. +- `request_rate_target_value`: (default: `100`) target value for the request rate. +- `request_rate_window_seconds`: (default: `60`) aggregation window for the request rate calculation. +- `concurrency_target_value`: (default: `100`) target value for the request concurrency. + +Note that due to Keda limitations, scaling is done by _either_ `concurrency` or `request_rate`. + +#### Workload Autoscaling Authentication + +Most Keda triggers require some form of authentication to query for data. In the Kubernetes API, they are represented by `TriggerAuthentication` and `ClusterTriggerAuthentication` resources. Dokku can manage these via the `scheduler-k3s:autoscaling-auth` commands, and includes generated resources with each helm release generated by a deploy. + +If no app-specific authentication is provided for a given trigger type, Dokku will fallback to any globally defined `ClusterTriggerAuthentication` resources. Autoscaling triggers within an app all share the same `TriggerAuthentication` resources, while `ClusterTriggerAuthentication` resources can be shared across all apps deployed by Dokku within a given cluster. + +##### Creating Authentication Resources + +Users can specify custom authentication resources directly via the Kubernetes api _or_ use the `scheduler-k3s:autoscaling-auth:set` command to create the resources in the Kubernetes cluster. + +```shell +dokku scheduler-k3s:autoscaling-auth:set $APP $TRIGGER --metadata apiKey=some-api-key --metadata appKey=some-app-key +``` + +For example, the following will configure the authentication for all datadog triggers on the specified app: + +```shell +dokku scheduler-k3s:autoscaling-auth:set node-js-app datadog --metadata apiKey=1234567890 --metadata appKey=asdfghjkl --metadata datadogSite=us5.datadoghq.com +``` + +After execution, Dokku will include the following resources for each specified trigger with the helm release generated on subsequent app deploys: + +- `Secret`: an Opaque `Secret` resource storing the authentication credentials +- `TriggerAuthentication`: A `TriggerAuthentication` resource that references the secret for use by triggers + +If the `--global` flag is specified instead of an app name, a custom helm chart is created on the fly with the above resources. + +##### Removing Authentication Resources + +To remove a configured authenticatin resource, run the `scheduler-k3s:autoscaling-auth:set` command with no metadata specified. Subsequent deploys will not include these resources. + +```shell +dokku scheduler-k3s:autoscaling-auth:set $APP $TRIGGER_TYPE +``` + +##### Displaying an Authentication Resource report + +To see a list of authentication resources managed by Dokku, run the `scheduler-k3s:autoscaling-auth:report` command. Without arguments, the report iterates every app; passing an app name (or `--global`) scopes the report: + +```shell +dokku scheduler-k3s:autoscaling-auth:report +dokku scheduler-k3s:autoscaling-auth:report node-js-app +dokku scheduler-k3s:autoscaling-auth:report --global +``` + +``` +====> node-js-app autoscaling-auth information + Datadog: configured +``` + +By default, the report will not display configured metadata - making it safe to include in Dokku report output. To include metadata and their values, add the `--include-metadata` flag: + +```shell +dokku scheduler-k3s:autoscaling-auth:report node-js-app --include-metadata +``` + +``` +====> node-js-app autoscaling-auth information + Datadog: configured + Datadog apiKey: 1234567890 + Datadog appKey: asdfghjkl + Datadog datadogSite: us5.datadoghq.com +``` + +JSON output emits flat keys of the form `{trigger}.{metadata_key}` and includes the actual metadata values so export tools can reconstruct the configured state: + +```shell +dokku scheduler-k3s:autoscaling-auth:report node-js-app --format json +``` + +A single configured metadata key can also be queried with a flag of the form `--scheduler-k3s-autoscaling-auth.{trigger}.{metadata_key}`. The returned value is masked in the same way as stdout output; use `--format json` to read the actual value: + +```shell +dokku scheduler-k3s:autoscaling-auth:report node-js-app --scheduler-k3s-autoscaling-auth.datadog.apiKey +``` + +### Integrating Kustomize + +Dokku supports integration with [Kustomize](https://kustomize.io/) to further customize the generated helm charts for app deployments. For example, a `config/kustomize/kustomization.yaml` file with the following contents will override the scale for each process deployed to `3`: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- rendered.yaml +patches: +- patch: |- + - op: replace + path: /spec/replicas + value: 3 + target: + group: apps + version: v1 + kind: Deployment +``` + +When using Kustomize with Dokku, all Kustomize-files _must_ be placed in the `config/kustomize` folder, with a `kustomization.yaml` file being the entrypoint to Kustomize. Dokku will render the helm chart to a `rendered.yaml` file, and then execute Kustomize with the `config/kustomize` folder as the context. + +See the [Kustomize](https://kustomize.io/) website for more details on how to use Kustomize. + +### Using kubectl remotely + +> [!WARNING] +> Certain ports must be open for interacting with the remote kubernets api. Refer to the [K3s networking documentation](https://docs.k3s.io/installation/requirements?os=debian#networking) for the required open ports between servers prior to running the command. + +By default, Dokku assumes that all it controls all actions on the cluster, and thus does not expose the `kubectl` binary for administrators. To interact with kubectl, you will need to retrieve the `kubeconfig` for the cluster and configure your client to use that configuration. + +```shell +dokku scheduler-k3s:show-kubeconfig +``` + +### Interacting with an external Kubernetes cluster + +While the k3s scheduler plugin is designed to work with a Dokku-managed k3s cluster, Dokku can be configured to interact with any Kubernetes cluster by setting the global `kubeconfig-path` to a path to a custom kubeconfig on the Dokku server. This property is only available at a global level. + +```shell +dokku scheduler-k3s:set --global kubeconfig-path /path/to/custom/kubeconfig +``` + +To set the default value, omit the value from the `scheduler-k3s:set` call: + +```shell +dokku scheduler-k3s:set --global kubeconfig-path +``` + +The default value for the `kubeconfig-path` is the k3s kubeconfig located at `/etc/rancher/k3s/k3s.yaml`. + +### Customizing the Kubernetes context + +When interacting with a custom Kubeconfig, the `kube-context` property can be set to specify a specific context within the kubeconfig to use. This property is available only at the global leve. + +```shell +dokku scheduler-k3s:set --global kube-context lollipop +``` + +To set the default value, omit the value from the `scheduler-k3s:set` call: + +```shell +dokku scheduler-k3s:set --global kube-context +``` + +The default value for the `kube-context` is an empty string, and will result in Dokku using the current context within the kubeconfig. + +### Customizing Helm Chart Properties + +Dokku includes a number of helm charts by default with settings that are optimized for Dokku. That said, it may be useful to further customize the charts for a given environment. Chart overrides are managed via the `scheduler-k3s:charts:set` command, which takes a `.` argument and a value. Chart overrides are always global because helm charts are not managed on a per-app basis. + +```shell +dokku scheduler-k3s:charts:set cert-manager.version 1.13.3 +``` + +> [!NOTE] +> Properties follow dot-notation, and are expanded according to Helm's internal logic. See the [Helm documentation](https://helm.sh/docs/helm/helm_install/#helm-install) for `helm install` for further details. + +Property names may contain `/` (e.g. for Kubernetes-style annotation keys such as `service.annotations.prometheus.io/scrape`) and values may span multiple lines; both are preserved verbatim. + +To unset a chart property, omit the value from the `scheduler-k3s:charts:set` call: + +```shell +dokku scheduler-k3s:charts:set cert-manager.version +``` + +Configured chart overrides can be inspected with the `scheduler-k3s:charts:report` command. Without an argument, it lists every chart known to Dokku along with any configured overrides; passing a chart name scopes the report to that chart: + +```shell +dokku scheduler-k3s:charts:report +dokku scheduler-k3s:charts:report cert-manager +dokku scheduler-k3s:charts:report --format json +``` + +> [!NOTE] +> The legacy form `dokku scheduler-k3s:set --global chart.. ` continues to work but is deprecated and will be removed in a future major release. Migrate to `scheduler-k3s:charts:set` instead. + +A `scheduler-k3s:ensure-charts` command with the `--force` flag is required after changing any chart properties in order to have them apply. This will install all charts, not just the ones that have changed. + +```shell +dokku scheduler-k3s:ensure-charts --force +``` + +Alternatively, a comma separated list of chart names can be specified to only force install the specified charts: + +```shell +dokku scheduler-k3s:ensure-charts --charts cert-manager +``` + +### Persistent storage + +K3s apps mount persistent volumes through the Dokku storage plugin. A `storage:create --scheduler k3s` call provisions a PersistentVolumeClaim through a small per-entry helm release; `storage:mount --container-dir ` then attaches it to an app. Because storage entries are global, the same PVC can be mounted into multiple apps (for `ReadWriteMany` workloads) or into the same app at multiple subpaths. + +```shell +# dynamically-provisioned PVC backed by Longhorn +dokku storage:create demo-data \ + --scheduler k3s \ + --size 2Gi \ + --access-mode ReadWriteOnce \ + --storage-class-name longhorn + +dokku storage:mount demo demo-data --container-dir /data +dokku storage:wait demo-data +git push dokku master +``` + +For a hostPath-backed PV (no StorageClass), pass `` as the second positional argument and omit `--storage-class-name`. The plugin renders both the PV and the PVC into the entry's helm release. The `--reclaim-policy` flag (`Retain` or `Delete`) controls whether the underlying PV survives `storage:destroy`. Annotations and labels on `storage:create` / `storage:set` propagate to both the PVC and the PV so backup tools (Velero, Longhorn snapshots) can find them. + +The legacy `storage:mount :` colon form is rejected on k3s apps; create a named entry instead. See [Persistent Storage](/docs/advanced-usage/persistent-storage.md) for the full command reference. + +### Chart upgrade callbacks + +Some chart version transitions require side-effects that a plain `helm upgrade` cannot perform. For example, the upgrade of `keda-add-ons-http` to `0.12.2` introduces breaking changes to deployment selectors, which are immutable in Kubernetes; the chart-managed deployments must be deleted before the upgrade can proceed. + +Dokku handles these cases internally by registering version-targeted pre-upgrade and post-upgrade callbacks against affected charts. When `scheduler-k3s:ensure-charts` runs, the installed chart version is compared against the registered callbacks: any whose target version is greater than the currently installed version and at most the configured chart version are executed in ascending semver order, each bracketed around an upgrade to that intermediate version. A final upgrade to the configured chart version then completes the run. + +These callbacks are not user-configurable. Failures during a callback or its surrounding upgrade abort the run, and a subsequent `scheduler-k3s:ensure-charts` resumes from the current state. + +## Scheduler Interface + +The following sections describe implemented and unimplemented scheduler functionality for the `k3s` scheduler. + +### Implemented Commands and Triggers + +This plugin implements various functionality through `plugn` triggers to integrate with Docker for running apps on a single server. The following functionality is supported by the `scheduler-k3s` plugin. + +- `apps:clone` +- `apps:destroy` +- `apps:rename` +- `docker-options`: + - The following docker options are translated into their kubernetes equivalents: + - `--cap-add` + - `--cap-drop` + - `--privileged` +- `cron` +- `enter` +- `deploy` +- healthchecks + - Due to Kubernetes limitations, only a single healthcheck is supported for each of the `liveness`, `readiness`, and `startup` healthchecks + - Due to Kubernetes limitations, content checks are not supported + - Ports specified in the `app.json` are ignored in favor of the container port on the port mapping detected +- `logs` +- `nginx` + - Properties set by the `nginx` plugin will be respected, either by turning them into annotations or creating a custom server/location snippet that the `ingress-nginx` project can use. A `ps:restart` after changing any nginx properties is required in order to have them apply. + - The `nginx:access-logs` and `nginx:error-logs` commands will fetch logs from one running `ingress-nginx` pod. + - The `nginx:show-config` command will retrieve any `server` blocks associated with a domain attached to the app from one running `ingress-nginx` pod. +- `ps:stop` +- `run` + - The `scheduler-post-run` trigger is not always triggered +- `run:detached` +- `run:list` + +### Unimplemented command functionality + +- `run:logs` +- `ps:inspect` + +The following Dokku functionality is not implemented at this time. + +- `vector` log integration +- persistent storage + +### Logging support + +App logs for the `logs` command are fetched by Dokku from running containers via the Kubernetes api. While the `k3s` scheduler does not integrate with the `logs:vector-*` subcommands, it does respect the global `vector-sink` logs property. When that property is set, the `scheduler-k3s:ensure-charts` command can be utilized to reconfigure vector to ship kubernetes logs to the provided sink. + +To setup log shipping, configure the global `vector-sink` property for the `logs` plugin.. Note that this can be run before _or_ after the `scheduler-k3s:ensure-charts` command - if run before, the `scheduler-k3s:ensure-charts` subcommand will pick up the value. + +```shell +dokku logs:set --global vector-sink "console://?encoding[codec]=json" +``` + +Next, run the `scheduler-k3s:ensure-charts` command with the `vector` chart to force the `k3s` scheduler to reconfigure vector with the specified sink: + +```shell +dokku scheduler-k3s:ensure-charts --charts vector +``` + +Please see the [vector logs documentation](/docs/deployment/logs.md#configuring-a-log-sink) for more information on specifying vector sinks. + +### Supported Resource Management Properties + +The `k3s` scheduler supports a minimal list of resource _limits_ and _reservations_: + +- cpu: is specified in number of CPUs a process can access. +- memory: should be specified with a suffix of `b` (bytes), `Ki` (kilobytes), `Mi` (megabytes), `Gi` (gigabytes). Default unit is `Mi` (megabytes). + +If unspecified for any task, the default reservation will be `.1` CPU and `128Mi` RAM, with no limit set for either CPU or RAM. This is to avoid issues with overscheduling pods on a cluster. To avoid issues, set more specific values for at least resource reservations. If unbounded utilization is desired, set CPU and Memory to `0m` and `0Mi`, respectively. + +> [!NOTE] +> Cron tasks retrieve resource limits based on the computed cron task ID. + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `scheduler-k3s:report`. The JSON keys emitted by `scheduler-k3s:report --format json` are the same names with the leading `--scheduler-k3s-` stripped (e.g. `deploy-timeout`, `global-deploy-timeout`, `computed-deploy-timeout`). Legacy keys with the `scheduler-k3s-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `deploy-timeout` | app + global | `300s` | `--scheduler-k3s-deploy-timeout`, `--scheduler-k3s-global-deploy-timeout`, `--scheduler-k3s-computed-deploy-timeout` | Timeout for a single helm install/upgrade cycle | +| `image-pull-secrets` | app + global | none | `--scheduler-k3s-image-pull-secrets`, `--scheduler-k3s-global-image-pull-secrets`, `--scheduler-k3s-computed-image-pull-secrets` | Comma-separated list of Kubernetes secret names used to pull private images | +| `ingress-class` | global only | `nginx` | `--scheduler-k3s-global-ingress-class`, `--scheduler-k3s-computed-ingress-class` | IngressClass name used for app ingresses (e.g. `nginx`, `traefik`) | +| `kube-context` | global only | none | `--scheduler-k3s-global-kube-context`, `--scheduler-k3s-computed-kube-context` | Kube context name used by helm and kubectl invocations | +| `kubeconfig-path` | global only | `/etc/rancher/k3s/k3s.yaml` | `--scheduler-k3s-global-kubeconfig-path`, `--scheduler-k3s-computed-kubeconfig-path` | Filesystem path to the kubeconfig used to talk to the cluster | +| `kustomize-root-path` | app + global | `config/kustomize` | `--scheduler-k3s-kustomize-root-path`, `--scheduler-k3s-global-kustomize-root-path`, `--scheduler-k3s-computed-kustomize-root-path` | Path within the app to a kustomize root applied after the helm install | +| `letsencrypt-email-prod` | app + global | none | `--scheduler-k3s-letsencrypt-email-prod`, `--scheduler-k3s-global-letsencrypt-email-prod`, `--scheduler-k3s-computed-letsencrypt-email-prod` | Contact email for production certificates. App-level values render a per-app namespaced Issuer; otherwise the shared production ClusterIssuer is used | +| `letsencrypt-email-stag` | app + global | none | `--scheduler-k3s-letsencrypt-email-stag`, `--scheduler-k3s-global-letsencrypt-email-stag`, `--scheduler-k3s-computed-letsencrypt-email-stag` | Contact email for staging certificates. App-level values render a per-app namespaced Issuer; otherwise the shared staging ClusterIssuer is used | +| `letsencrypt-server` | app + global | `prod` | `--scheduler-k3s-letsencrypt-server`, `--scheduler-k3s-global-letsencrypt-server`, `--scheduler-k3s-computed-letsencrypt-server` | ACME directory (`prod` or `staging`) used for app certificates | +| `namespace` | app + global | `default` | `--scheduler-k3s-namespace`, `--scheduler-k3s-global-namespace`, `--scheduler-k3s-computed-namespace` | Kubernetes namespace into which the app's resources are installed | +| `network-interface` | global only | `eth0` | `--scheduler-k3s-global-network-interface`, `--scheduler-k3s-computed-network-interface` | Host network interface used by k3s | +| `rollback-on-failure` | app + global | `false` | `--scheduler-k3s-rollback-on-failure`, `--scheduler-k3s-global-rollback-on-failure`, `--scheduler-k3s-computed-rollback-on-failure` | When `true`, helm rolls back the release if a deploy fails | +| `shm-size` | app + global | none | `--scheduler-k3s-shm-size`, `--scheduler-k3s-global-shm-size`, `--scheduler-k3s-computed-shm-size` | `/dev/shm` size override applied to app containers | +| `token` | global only | none | `--scheduler-k3s-global-token` (masked as `*******` in default stdout output; the raw value is returned when queried via `--format json` or when this flag is requested explicitly) | Cluster join token used by `scheduler-k3s:cluster-add` | +| `chart..` | global only | none | `--scheduler-k3s-global-chart..` (dynamic per chart/property) | Override a value injected into the helm chart named `` (one row per chart/property pair). Manage these via the dedicated `scheduler-k3s:charts:set` / `scheduler-k3s:charts:report` commands; the `scheduler-k3s:set`/`scheduler-k3s:report` form is deprecated. | diff --git a/docs/deployment/schedulers/kubernetes.md b/docs/deployment/schedulers/kubernetes.md index d0fdc9d6086..bad07d4bdfa 100644 --- a/docs/deployment/schedulers/kubernetes.md +++ b/docs/deployment/schedulers/kubernetes.md @@ -1,14 +1,25 @@ # Kubernetes Scheduler -> Warning: This scheduler is not in Dokku core and thus is considered experimental. +> [!WARNING] +> Deprecation notice: This scheduler has been replaced with the [k3s scheduler plugin](/docs/deployment/schedulers/k3s.md) and is no longer under active development. -The [Kubernetes Scheduler Plugin](https://github.com/dokku/dokku-scheduler-kubernetes) implements the following functionality: +The [Kubernetes Scheduler Plugin](https://github.com/dokku/dokku-scheduler-kubernetes) is available free as an external plugin. Please see the plugin's [issue tracker](https://github.com/dokku/dokku-scheduler-kubernetes/issues) for more information on the status of the plugin. + +For users that require additional functionality, please refer to the [Sponsoring Documentation](https://github.com/dokku/.github/blob/master/SPONSORING.md). + +## Scheduler Interface + +The following sections describe implemented scheduler functionality for the `kubernetes` scheduler. + +### Implemented Commands and Triggers + +This plugin implements various functionality through `plugn` triggers to integrate with `kubectl` for running apps on a Kubernetes cluster. The following functionality is supported by the `scheduler-kubernetes` plugin. - `apps:destroy` - `deploy`: partial, does not implement failed deploy log capture - `logs`: partial, does not implement failure logs - `ps:stop` -Please see the plugin's [issue tracker](https://github.com/dokku/dokku-scheduler-kubernetes/issues) for more information on the status of the plugin. +### Logging support -For users that require additional functionality, please refer to the [Sponsoring Documentation](https://github.com/dokku/.github/blob/master/SPONSORING.md). +App logs for the `logs` command are fetched from running pods via the `kubectl` cli. To persist logs across deployments, consider using [Vector](https://vector.dev/docs/setup/installation/platforms/kubernetes/) or a similar tool to ship logs to another service or a third-party platform. diff --git a/docs/deployment/schedulers/nomad.md b/docs/deployment/schedulers/nomad.md index 3a3280f2b24..95de76e9c58 100644 --- a/docs/deployment/schedulers/nomad.md +++ b/docs/deployment/schedulers/nomad.md @@ -1,13 +1,25 @@ # Nomad Scheduler -> Warning: This scheduler is not in Dokku core and thus is considered experimental. +> [!WARNING] +> This scheduler is not in Dokku core and thus functionality may change over time as the API stabilizes. -The [Nomad Scheduler Plugin](https://github.com/dokku/dokku-scheduler-nomad) implements the following functionality: +The [Nomad Scheduler Plugin](https://github.com/dokku/dokku-scheduler-nomad) is available free as an external plugin. Please see the plugin's [issue tracker](https://github.com/dokku/dokku-scheduler-nomad/issues) for more information on the status of the plugin. + +For users that require additional functionality, please refer to the [Sponsoring Documentation](https://github.com/dokku/.github/blob/master/SPONSORING.md). + +## Scheduler Interface + +The following sections describe implemented scheduler functionality for the `nomad` scheduler. + +### Implemented Commands and Triggers + +This plugin implements various functionality through `plugn` triggers to integrate with the `nomad` cli for running apps on a Nomad cluster. The following functionality is supported by the `scheduler-nomad` plugin. - `apps:destroy` - `deploy` - `ps:stop` -Please see the plugin's [issue tracker](https://github.com/dokku/dokku-scheduler-nomad/issues) for more information on the status of the plugin. +### Logging support -For users that require additional functionality, please refer to the [Sponsoring Documentation](https://github.com/dokku/.github/blob/master/SPONSORING.md). +> [!WARNING] +> Fetching app logs for the `logs` command is currently not implemented. Please consider using [Vector](https://vector.dev/docs/setup/installation/platforms/kubernetes/) or a similar tool to ship logs to another service or a third-party platform. diff --git a/docs/deployment/schedulers/null.md b/docs/deployment/schedulers/null.md index b17ba5c9198..eeb9bb6abf5 100644 --- a/docs/deployment/schedulers/null.md +++ b/docs/deployment/schedulers/null.md @@ -1,5 +1,6 @@ # Null Scheduler +> [!IMPORTANT] > New as of 0.25.0 The `null` scheduler does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ip:port-combination) for more information on the aforementioned use case. diff --git a/docs/deployment/schedulers/scheduler-management.md b/docs/deployment/schedulers/scheduler-management.md index 132720ed75a..fa838a73271 100644 --- a/docs/deployment/schedulers/scheduler-management.md +++ b/docs/deployment/schedulers/scheduler-management.md @@ -1,5 +1,6 @@ # Scheduler Management +> [!IMPORTANT] > New as of 0.26.0 ``` @@ -18,8 +19,6 @@ Dokku supports the following built-in schedulers: - `scheduler-docker-local`: Schedules apps against the local docker socket and runs containers directly on the Dokku host. See the [docker-local scheduler documentation](/docs/deployment/schedulers/docker-local.md) for more information on how this scheduler functions. - `scheduler-null`: Does nothing during the scheduler phase. See the [null scheduler documentation](/docs/deployment/schedulers/null.md) for more information on how this scheduler functions. -See the [alternate schedulers documentation](/docs/deployment/schedulers/alternate-schedulers.md) for more information on other scheduler plugins. - ### Overriding the auto-selected scheduler If desired, the scheduler can be specified via the `scheduler:set` command by speifying a value for `selected`. The selected scheduler will always be used. @@ -46,6 +45,26 @@ The default value may be set by passing an empty value for the option. dokku scheduler:set --global selected ``` +### Setting the app shell + +The shell used for `dokku run` and `dokku enter` invocations against an app can be customized via the `shell` property. This is useful for images that ship with a non-default shell - for example, alpine-based images that only have `/bin/sh`. + +```shell +dokku scheduler:set node-js-app shell sh +``` + +The `shell` property can also be set globally: + +```shell +dokku scheduler:set --global shell sh +``` + +The default value (an empty string, which lets the scheduler pick its own default) may be restored by passing an empty value: + +```shell +dokku scheduler:set node-js-app shell +``` + ### Displaying scheduler reports for an app You can get a report about the app's scheduler status using the `scheduler:report` command: @@ -61,14 +80,16 @@ dokku scheduler:report Scheduler selected: herokuish =====> python-sample scheduler information Scheduler computed selected: dockerfile - Scheduler global selected: herokuish + Scheduler global selected: Scheduler selected: dockerfile =====> ruby-sample scheduler information - Scheduler computed selected: herokuish - Scheduler global selected: herokuish + Scheduler computed selected: docker-local + Scheduler global selected: Scheduler selected: ``` +The `selected` and `global-selected` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-selected` key holds the effective value used at deploy time, falling back to the global value (where one has been set) and then to the built-in default of `docker-local`. + You can run the command for a specific app also. ```shell @@ -77,6 +98,8 @@ dokku scheduler:report node-js-app ``` =====> node-js-app scheduler information + Scheduler computed selected: herokuish + Scheduler global selected: Scheduler selected: herokuish ``` @@ -93,26 +116,46 @@ Custom plugins names _must_ have the prefix `scheduler-` or scheduler overriding At this time, the following dokku commands are used to implement a complete scheduler. - `apps:destroy`: stops the app processes on the scheduler - - triggers: post-delete, scheduler-register-retired, scheduler-retire + - triggers: post-delete, scheduler-register-retired, scheduler-retire - `apps:rename`: handles app renaming - - triggers: post-app-rename-setup + - triggers: post-app-rename-setup - `apps:clone`: handles app cloning - - triggers: post-app-clone-setup + - triggers: post-app-clone-setup +- `cron`: generates cron tasks for the app + - triggers: scheduler-cron-write - `deploy`: deploys app proceses and checks the status of a deploy - - triggers: scheduler-app-status, scheduler-deploy, scheduler-is-deployed, scheduler-logs-failed + - triggers: scheduler-app-status, scheduler-deploy, scheduler-is-deployed, scheduler-logs-failed - `enter`: enters a running container - - triggers: scheduler-enter + - triggers: scheduler-enter - `logs`: fetches app logs - - triggers: scheduler-logs + - triggers: scheduler-logs - `run`: starts one-off run containers (detached and non-detached) as well as listing run processes - - triggers: scheduler-run, scheduler-run-list + - triggers: scheduler-run, scheduler-run-list +- `run:list`: lists running containers + - triggers: scheduler-run-list +- `run:logs`: fetches running container logs + - triggers: scheduler-run-logs - `ps:stop`: stops app processes - - triggers: scheduler-stop + - triggers: scheduler-stop, scheduler-cron-write +- `ps:start`: starts app processes + - triggers: scheduler-deploy, scheduler-cron-write - `ps:inspect`: outputs inspect output for processes in an app - - triggers: scheduler-inspect + - triggers: scheduler-inspect Schedulers may decide to omit some functionality here, or use plugin triggers to supplement config with information from other plugins. Additionally, a scheduler may implement other triggers in order handle any extra processes needed during a deploy. Schedulers can use any tools available on the system to build the docker image, and may even be used to interact with off-server systems. The only current requirement is that the scheduler must have access to the image built in the build phase. If this is not the case, the registry plugin can be used to push the image to a registry that the scheduler software can access. Deployment tasks are currently executed directly on the primary Dokku server. + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `scheduler:report`. The JSON keys emitted by `scheduler:report --format json` are the same names with the leading `--scheduler-` stripped (e.g. `selected`, `global-selected`, `computed-selected`). Legacy keys with the `scheduler-` prefix are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `selected` | app + global | `docker-local` | `--scheduler-selected`, `--scheduler-global-selected`, `--scheduler-computed-selected` | Scheduler plugin used to deploy this app (`docker-local`, `k3s`, etc.) | +| `shell` | app + global | none | `--scheduler-shell`, `--scheduler-global-shell`, `--scheduler-computed-shell` | Shell used by `enter`/`run` commands when entering a container | diff --git a/docs/deployment/user-management.md b/docs/deployment/user-management.md index 356cc60e7d5..27922f67c7b 100644 --- a/docs/deployment/user-management.md +++ b/docs/deployment/user-management.md @@ -1,5 +1,6 @@ # User Management +> [!IMPORTANT] > New as of 0.7.0 ``` @@ -34,6 +35,7 @@ The output contains the following information: - The `KEY_NAME`. - A comma separated list of SSH options under the `SSHCOMMAND_ALLOWED_KEYS` name. +> [!IMPORTANT] > New as of 0.20.2 The keys for a specific user may be listed by specifying a second argument to the `ssh-keys:list` command: @@ -42,7 +44,8 @@ The keys for a specific user may be listed by specifying a second argument to th dokku ssh-keys:list admin ``` -> 0.22.3 +> [!IMPORTANT] +> New as of 0.22.3 The output format may be specified via the `--format` flag. Supported values include `json` and `text`. @@ -58,7 +61,7 @@ dokku ssh-keys:list --format json admin ### Adding SSH keys -You can add your public key to Dokku with the `ssh-keys:add` command. The output will be the fingerprint of the SSH key: +You can add your public key to Dokku with the `ssh-keys:add` command. The path specified must contain your desired public ssh key contents and must exist on the server. Any special characters - such as `~` - are interpreted under the `dokku` user context. The output will be the fingerprint of the SSH key: ```shell dokku ssh-keys:add KEY_NAME path/to/id_rsa.pub @@ -72,6 +75,24 @@ SHA256:ABC123ABC123+abc123abc123Zabc123abcZ123abc > `KEY_NAME` is a unique name which is used to identify public keys. Attempting to re-use a key name will result in an error. The SSH (Git) user is *always* `dokku`, as this is the system user that the `dokku` binary uses to perform all its actions. +Alternatively, you may pipe the contents of your ssh key via `cat` or `echo` into the `ssh-keys:add` command instead of specifying a path to the ssh key: + +```shell +echo "$CONTENTS_OF_YOUR_PUBLIC_SSH_KEY_HERE" | dokku ssh-keys:add KEY_NAME +``` + +``` +SHA256:ABC123ABC123+abc123abc123Zabc123abcZ123abc +``` + +```shell +cat path/to/id_rsa.pub | dokku ssh-keys:add KEY_NAME +``` + +``` +SHA256:ABC123ABC123+abc123abc123Zabc123abcZ123abc +``` + Admin users and root can add keys remotely by specifying the `dokku` bin on their `ssh` command: ```shell diff --git a/docs/deployment/zero-downtime-deploys.md b/docs/deployment/zero-downtime-deploys.md index 93c31556886..c737f56e118 100644 --- a/docs/deployment/zero-downtime-deploys.md +++ b/docs/deployment/zero-downtime-deploys.md @@ -1,18 +1,20 @@ -# Zero Downtime Deploys +# Zero Downtime Deploy Checks +> [!IMPORTANT] > New as of 0.5.0 ``` -checks:disable [process-type(s)] Disable zero-downtime deployment for all processes (or comma-separated process-type list) ***WARNING: this will cause downtime during deployments*** -checks:enable [process-type(s)] Enable zero-downtime deployment for all processes (or comma-separated process-type list) -checks:report [] [] Displays a checks report for one or more apps -checks:run [process-type(s)] Runs zero-downtime checks for all processes (or comma-separated process-type list) -checks:skip [process-type(s)] Skip zero-downtime checks for all processes (or comma-separated process-type list) +checks:disable [process-type(s)] Disable zero-downtime deployment for all processes (or comma-separated process-type list) ***WARNING: this will cause downtime during deployments*** +checks:enable [process-type(s)] Enable zero-downtime deployment for all processes (or comma-separated process-type list) +checks:report [] [] Displays a checks report for one or more apps +checks:run [process-type(s)] Runs zero-downtime checks for all processes (or comma-separated process-type list) +checks:set [--global|] Set or clear a logs property for an app +checks:skip [process-type(s)] Skip zero-downtime checks for all processes (or comma-separated process-type list) ``` By default, Dokku will wait `10` seconds after starting each container before assuming it is up and proceeding with the deploy. Once this has occurred for all containers started by an application, traffic will be switched to point to your new containers. Dokku will also wait a further `60` seconds *after* the deploy is complete before terminating old containers in order to give time for long running connections to terminate. In either case, you may have more than one container running for a given application. -You may both create user-defined checks for web processes using a `CHECKS` file, as well as customize any and all parts of this experience using the checks plugin. +You may both create user-defined checks for web processes using the `healthchecks` key in the `app.json` file, as well as customize any and all parts of this experience using the checks plugin. > Web checks are performed via `curl` on Dokku host. Some application code - such > as the Django framework - checks for specific hostnames or header values, these @@ -22,19 +24,35 @@ You may both create user-defined checks for web processes using a `CHECKS` file, > - Allow checks from all hostnames: Modify your application to accept a dynamically provided hostname. > - Specify the domain within the check: See below for further documentation. -## Configuring check settings using the `config` plugin +## Configuring checks settings -There are certain settings that can be configured via environment variables: +### wait-to-retire -- `DOKKU_DEFAULT_CHECKS_WAIT`: (default: `10`) If no user-defined checks are specified - or if the process being checked is not a `web` process - this is the period of time Dokku will wait before checking that a container is still running. -- `DOKKU_DOCKER_STOP_TIMEOUT`: (default: `10`) Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands *also* respect this value. If not specified, the Docker defaults for the [`docker stop` command](https://docs.docker.com/engine/reference/commandline/stop/) will be used. -- `DOKKU_WAIT_TO_RETIRE`: (default: `60`) After a successful deploy, the grace period given to old containers before they are stopped/terminated. This is useful for ensuring completion of long-running HTTP connections. +After a successful deploy, Dokku immediately sends `SIGTERM` to old containers so the application can begin a graceful shutdown (draining in-flight connections, flushing buffers, and so on) the moment proxy traffic switches to the new containers. The old containers are then left running for the `wait-to-retire` grace period before being stopped, which sends `SIGTERM` again and ultimately `SIGKILL` after `stop-timeout-seconds`. This mirrors Heroku's [dyno shutdown behavior](https://devcenter.heroku.com/articles/dyno-shutdown-behavior). -The following settings may also be specified in the `CHECKS` file, though are available as environment variables in order to ease application reuse. +The `wait-to-retire` value controls the grace period and is useful for ensuring completion of long-running HTTP connections. -- `DOKKU_CHECKS_WAIT`: (default: `5`) Wait this many seconds for the container to start before running checks. -- `DOKKU_CHECKS_TIMEOUT`: (default: `30`) Wait this many seconds for each response before marking it as a failure. -- `DOKKU_CHECKS_ATTEMPTS`: (default: `5`) Number of retries for to run for a specific check before marking it as a failure +```shell +dokku checks:set node-js-app wait-to-retire 30 +``` + +Defaults to `60`. + +## Setting stop grace period + +You can set the `stop-timeout-seconds` property on the `ps` plugin to change this value (default: `30`). See the [process management documentation](/docs/processes/process-management.md#changing-process-management-settings) for more information. + +## Configuring check settings + +There are certain settings that can be configured via the `checks` plugin properties: + +- `default-wait`: (default: `10`) If no user-defined checks are specified - or if the process being checked is not a `web` process - this is the period of time Dokku will wait before checking that a container is still running. Set globally via `dokku checks:set --global default-wait `. + +The following settings may also be specified in the `app.json` file, though are available as properties in order to ease application reuse. + +- `wait`: (default: `5`) Wait this many seconds for the container to start before running checks. Set via `dokku checks:set wait `. +- `timeout`: (default: `30`) Wait this many seconds for each response before marking it as a failure. Set via `dokku checks:set timeout `. +- `attempts`: (default: `5`) Number of retries for to run for a specific check before marking it as a failure. Set via `dokku checks:set attempts `. ## Skipping and Disabling Checks @@ -49,10 +67,6 @@ dokku checks:skip node-js-app worker,web ``` -----> Skipping zero downtime for app's (node-js-app) proctypes (worker,web) ------> Unsetting node-js-app ------> Unsetting DOKKU_CHECKS_DISABLED ------> Setting config vars - DOKKU_CHECKS_SKIPPED: worker,web ``` Zero downtime checks can also be disabled completely. This will stop old containers *before* new ones start, which may result in broken connections and downtime if your application fails to boot properly. @@ -63,14 +77,11 @@ dokku checks:disable node-js-app worker ``` -----> Disabling zero downtime for app's (node-js-app) proctypes (worker) ------> Setting config vars - DOKKU_CHECKS_DISABLED: worker ------> Setting config vars - DOKKU_CHECKS_SKIPPED: web ``` ### Displaying checks reports for an app +> [!IMPORTANT] > New as of 0.8.1 You can get a report about the app's checks status using the `checks:report` command: @@ -82,15 +93,26 @@ dokku checks:report ``` =====> node-js-app checks information Checks disabled list: none - Checks skipped list: none + Checks skipped list: none + Checks computed wait to retire: 60 + Checks global wait to retire: + Checks wait to retire: =====> python-app checks information Checks disabled list: none - Checks skipped list: none + Checks skipped list: none + Checks computed wait to retire: 60 + Checks global wait to retire: + Checks wait to retire: =====> ruby-app checks information Checks disabled list: _all_ - Checks skipped list: none + Checks skipped list: none + Checks computed wait to retire: 60 + Checks global wait to retire: + Checks wait to retire: ``` +The `wait-to-retire` and `global-wait-to-retire` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-wait-to-retire` key holds the effective value used at deploy time, falling back to the global value (where one has been set) and then to the built-in default of `60`. + You can run the command for a specific app also. ```shell @@ -100,7 +122,10 @@ dokku checks:report node-js-app ``` =====> node-js-app checks information Checks disabled list: none - Checks skipped list: none + Checks skipped list: none + Checks computed wait to retire: 60 + Checks global wait to retire: + Checks wait to retire: ``` You can pass flags which will output only the value of the specific information you want. For example: @@ -111,97 +136,49 @@ dokku checks:report node-js-app --checks-disabled-list ## Customizing checks -If your application needs a longer period to boot up - perhaps to load data into memory, or because of slow boot time - you may also use Dokku's `checks` functionality to more precisely check whether an application can serve traffic or not. - -Checks are run against the detected `web` process from your application's `Procfile`. For non-web processes, Dokku will fallback to the aforementioned process uptime check. - -To specify checks, add a `CHECKS` file to the root of your project directory. The `CHECKS` file should be plain text and may contain: - -- check instructions -- settings (NAME=VALUE) -- comments (lines starting with #) -- empty lines - -> For Dockerfile and Docker Image based deploys, the file *must* be in the `WORKDIR` directory of the built image. `/app` is used by default as the root container directory for buildpack-based deploys. - -### Check instructions - -The format of a check instruction is a path or relative URL, optionally followed by the expected content: - -``` -/about Our Amazing Team -``` - -The `CHECKS` file can contain multiple checks: - -``` -/ My Amazing App -/stylesheets/index.css .body -/scripts/index.js $(function() -/images/logo.png -``` - -To check an application that supports multiple hostnames, use relative URLs that include the hostname: - -``` -//admin.dokku.me Admin Dashboard -//static.dokku.me/logo.png -``` - -You can also specify the protocol to explicitly check HTTPS requests: - -``` -https://admin.dokku.me Admin Dashboard -https://static.dokku.me/logo.png -``` - -While a full URL may be used in order to invoke checks, if you are using relative URLs, the port *must* be omitted. - -> Changed as of 0.22.5 - -Please note that dollar sign bracket characters (`{` and `}`) must be escaped when used within a `CHECKS` file. Escaping follows golang template rules. The proper way to do this is via one of the following methods: - -``` -# escaping the `{` character -# using double-quotes -{{"{"}} +> [!IMPORTANT] +> New as of 0.31.0 -# using raw string constants -{{`{`}} - -# escaping the `}` character -# using double-quotes -{{"}"}} - -# using raw string constants -{{`}`}} -``` - -### Templating Checks Files - -> New as of 0.22.5 - -An app's `CHECKS` file is sent through a single pass of the [`sigil`](https://github.com/gliderlabs/sigil/) templating tool. This enables usage of Golang templating within application `CHECKS` files. In addition to general templating access, access to app environment variables is also allowed via the `var` function: - -``` -{{ var "SOME_ENV_VAR" }} -``` - -This may be useful if certain zero-downtime checks require access to an app-specific value, such as a domain name. - -### Check settings - -The default behavior is to wait for `5` seconds before running the checks, to timeout the checks after `30` seconds, and to attempt the checks `5` times. If the checks fail `5` times, the deployment is considered failed and the old container will continue serving traffic. - -You can change the default behavior by setting `WAIT`, `TIMEOUT`, and `ATTEMPTS` to different values in the `CHECKS` file: - -``` -WAIT=30 # Wait 1/2 minute -TIMEOUT=60 # Timeout after a minute -ATTEMPTS=10 # Attempt checks 10 times +If your application needs a longer period to boot up - perhaps to load data into memory, or because of slow boot time - you may also use Dokku's `checks` functionality to more precisely check whether an application can serve traffic or not. -/ My Amazing App -``` +Healthchecks are run against all process from your application's `Procfile`. When no healthcheck is defined, Dokku will fallback to a process uptime check. + +One or more healthchecks can be defined in the `app.json` file - see the [deployment task documentation](/docs/advanced-usage/deployment-tasks.md) for more information on how this is extracted - under the `healthchecks.web` path: + +```json +{ + "healthchecks": { + "web": [ + { + "type": "startup", + "name": "web check", + "description": "Checking if the app responds to the /health/ready endpoint", + "path": "/health/ready", + "attempts": 3 + } + ] + } +} +``` + +A healthcheck entry takes the following properties: + +- `attempts`: (default: `3`) Number of retry attempts to perform on failure. +- `command`: (default `''` - empty string) Command to execute within container. +- `content`: (default: `''` - empty string) Content to search in http path check output. +- `initialDelay`: (default: 0, unit: seconds) Number of seconds to wait after a container has started before triggering the healthcheck. +- `name`: (default: autogenerated) The name of the healthcheck. If unspecified, it will be autogenerated from the rest of the healthcheck information. +- `path`: (default: `/` - for http checks): An http path to check. +- `port`: (default: `5000`): Port to run healthcheck against. +- `timeout`: (default: `5` seconds): Number of seconds to wait before timing out a healthcheck. +- `type`: (default: `""` - none): Type of the healthcheck. Options: liveness, readiness, startup. +- `uptime`: (default: `""` - none): Amount of time the container must be alive before the container is considered healthy. Any restarts will cause this to check to fail, and this check does not respect retries. +- `wait`: (default: `5` seconds): Number of seconds to wait between healthcheck attempts. + +> [!WARNING] +> Healthchecks are implemented by specific scheduler plugins, and not all plugins support all options. Please consult the scheduler documentation for further details on what is supported. + +See the [docker-container-healthchecker](https://github.com/dokku/docker-container-healthchecker) documentation for more details on how healthchecks are interpreted. ## Manually invoking checks @@ -289,181 +266,10 @@ dokku checks:run node-js-app web.3 Invalid container id specified (APP.web.3) ``` -## Example: Successful Rails deployment +## Properties -In this example, a Rails application is successfully deployed to Dokku. The initial round of checks fails while the server is starting, but once it starts they succeed and the deployment is successful. `WAIT` is set to `10` because our application takes a while to boot up. `ATTEMPTS` is set to `6`, but the third attempt succeeds. +### Settable properties -### Successful `CHECKS` file - -``` -WAIT=10 -ATTEMPTS=6 -/check.txt simple_check -``` - -For this check to work, we've added a line to `config/routes.rb` that simply returns a string: - -``` -get '/check.txt', to: proc {[200, {}, ['simple_check']]} -``` - -### Successful deploy output - -> Note: The output has been trimmed for brevity. - -```shell -git push dokku master -``` - -``` ------> Cleaning up... ------> Building node-js-app from herokuish... ------> Adding BUILD_ENV to build environment... ------> Ruby app detected ------> Compiling Ruby/Rails ------> Using Ruby version: ruby-2.0.0 - -..... - ------> Discovering process types - Procfile declares types -> web ------> Releasing node-js-app... ------> Deploying node-js-app... ------> Running pre-flight checks ------> Attempt 1/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/check.txt => "simple_check" - ! -curl: (7) Failed to connect to 172.17.0.155 port 5000: Connection refused - ! Check attempt 1/6 failed. ------> Attempt 2/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/check.txt => "simple_check" - ! -curl: (7) Failed to connect to 172.17.0.155 port 5000: Connection refused - ! Check attempt 2/6 failed. ------> Attempt 3/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/check.txt => "simple_check" ------> All checks successful! -=====> node-js-app container output: - => Booting Thin - => Rails 4.2.0 application starting in production on http://0.0.0.0:5000 - => Run `rails server -h` for more startup options - => Ctrl-C to shutdown server - Thin web server (v1.6.3 codename Protein Powder) - Maximum connections set to 1024 - Listening on 0.0.0.0:5000, CTRL+C to stop -=====> end node-js-app container output ------> Running post-deploy ------> Configuring myapp.dokku.me... ------> Creating http nginx.conf - Reloading nginx ------> Shutting down old container in 60 seconds -=====> Application deployed: - http://myapp.dokku.me -``` - -## Example: Failing Rails deployment - -In this example, a Rails application fails to deploy. The reason for the failure is that the PostgreSQL database connection fails. The initial checks will fail while we wait for the server to start up, just like in the above example. However, once the server does start accepting connections, we will see an error 500 due to the PostgreSQL database connection failure. - -Once the attempts have been exceeded, the deployment fails and we see the container output, which shows the PostgreSQL connection errors. - -### Failing `CHECKS` file - -``` -WAIT=10 -ATTEMPTS=6 -/ -``` - -> The check to the root url `/` would normally access the database. - -### Failing deploy output - -> Note: The output has been trimmed for brevity. - -```shell -git push dokku master -``` - -``` ------> Cleaning up... ------> Building node-js-app from herokuish... ------> Adding BUILD_ENV to build environment... ------> Ruby app detected ------> Compiling Ruby/Rails ------> Using Ruby version: ruby-2.0.0 - -..... - -Discovering process types -Procfile declares types -> web -Releasing node-js-app... -Deploying node-js-app... -Running pre-flight checks ------> Attempt 1/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (7) Failed to connect to 172.17.0.188 port 5000: Connection refused - ! Check attempt 1/6 failed. ------> Attempt 2/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (7) Failed to connect to 172.17.0.188 port 5000: Connection refused - ! Check attempt 2/6 failed. ------> Attempt 3/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (22) The requested URL returned error: 500 Internal Server Error - ! Check attempt 3/6 failed. ------> Attempt 4/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (22) The requested URL returned error: 500 Internal Server Error - ! Check attempt 4/6 failed. ------> Attempt 5/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (22) The requested URL returned error: 500 Internal Server Error - ! Check attempt 5/6 failed. ------> Attempt 6/6 Waiting for 10 seconds ... - CHECKS expected result: - http://localhost/ => "" - ! -curl: (22) The requested URL returned error: 500 Internal Server Error -Could not start due to 1 failed checks. - ! Check attempt 6/6 failed. -=====> node-js-app container output: - => Booting Thin - => Rails 4.2.0 application starting in production on http://0.0.0.0:5000 - => Run `rails server -h` for more startup options - => Ctrl-C to shutdown server - Thin web server (v1.6.3 codename Protein Powder) - Maximum connections set to 1024 - Listening on 0.0.0.0:5000, CTRL+C to stop - Started GET "/" for 172.17.42.1 at 2015-03-26 21:36:47 +0000 - Is the server running on host "172.17.42.1" and accepting - TCP/IP connections on port 5431? - PG::ConnectionBad (could not connect to server: Connection refused - Is the server running on host "172.17.42.1" and accepting - TCP/IP connections on port 5431? - ): - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:651:in `initialize' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:651:in `new' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:651:in `connect' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:242:in `initialize' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:44:in `new' - vendor/bundle/ruby/2.0.0/gems/activerecord-4.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:44:in `postgresql_connection -=====> end node-js-app container output -/usr/bin/dokku: line 49: 23409 Killed dokku deploy "$APP" -To dokku@dokku.me:myapp - ! [remote rejected] dokku -> master (pre-receive hook declined) -error: failed to push some refs to 'dokku@dokku.me:myapp' -``` +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `wait-to-retire` | app + global | `60` | `--checks-wait-to-retire`, `--checks-global-wait-to-retire`, `--checks-computed-wait-to-retire` | Seconds to wait between deploying the new container and stopping the old one | diff --git a/docs/development/architecture.md b/docs/development/architecture.md new file mode 100644 index 00000000000..2e8cdf9eb2c --- /dev/null +++ b/docs/development/architecture.md @@ -0,0 +1,316 @@ +# Dokku Architecture + +This document provides a developer-focused overview of Dokku's internal architecture. It is intended for contributors who want to understand how Dokku works under the hood before diving into the codebase. + +## High-Level Architecture + +Dokku is a Docker-powered Platform as a Service (PaaS) that provides a Heroku-like deployment experience. The following diagram shows the main components and their interactions: + +``` ++------------------+ +| User/Client | ++--------+---------+ + | + | SSH / git push + v ++--------+---------+ +| dokku binary | +| (bash script) | ++--------+---------+ + | + | parse_args / execute_dokku_cmd + v ++--------+---------+ +| Plugin System | +| (plugn) | ++--------+---------+ + | + | triggers / subcommands + v ++--------+---------+ +------------------+ +| Core Plugins |---->| Docker / K8s | +| (apps, git, | | Runtime | +| config, etc.) | +------------------+ ++------------------+ +``` + +**Key components:** + +- **dokku binary**: The main entry point (`/dokku`), a bash script that handles authentication, argument parsing, and routes commands to plugins +- **Plugin System**: Uses [plugn](https://github.com/dokku/plugn) to execute triggers and discover commands +- **Core Plugins**: Implement all Dokku functionality (apps, git, config, builders, schedulers, proxies, etc.) +- **Runtime**: Docker (default via `scheduler-docker-local`) or Kubernetes (via `scheduler-k3s`) + +## Directory Structure + +### Source Tree + +``` +dokku/ +├── dokku # Main CLI entry point (bash script) +├── plugins/ # All plugin source code +│ ├── apps/ # App management +│ ├── git/ # Git push handling +│ ├── config/ # Environment variables +│ ├── builder-*/ # Build backends (herokuish, pack, dockerfile, etc.) +│ ├── scheduler-*/ # Deployment schedulers (docker-local, k3s) +│ ├── *-vhosts/ # Proxy implementations (nginx, traefik, caddy, etc.) +│ └── common/ # Shared functions and utilities +├── docs/ # Documentation (markdown) +├── debian/ # Debian packaging files +├── contrib/ # Installation scripts and helpers +└── tests/ # Integration tests (bats) +``` + +### Runtime Directories + +When Dokku is installed, it creates the following directory structure: + +``` +/var/lib/dokku/ +├── core-plugins/ # Core plugin binaries (installed from source) +│ ├── available/ # All available core plugins +│ └── enabled/ # Symlinks to enabled plugins +├── plugins/ # Community plugins +│ ├── available/ +│ └── enabled/ +└── data/ # Plugin data storage + └── / # Per-plugin data + └── / # Per-app properties + +$DOKKU_ROOT (~dokku by default) +├── / # Per-app data +│ ├── refs/ # Git refs +│ ├── HEAD # Current git HEAD +│ ├── tls/ # SSL certificates +│ └── ... # Other app-specific files +└── .dokkurc/ # Global configuration overrides +``` + +### Plugin Directory Layout + +Each plugin follows a consistent structure: + +``` +plugins// +├── plugin.toml # Plugin metadata (description, version) +├── commands # Help output and catch-all command handler +├── subcommands/ # Individual command implementations +│ ├── default # Default command (e.g., `dokku apps`) +│ └── # Named commands (e.g., `dokku apps:create`) +├── functions # Public functions for other plugins to source +├── internal-functions # Private functions +├── triggers.go # Go-based trigger implementations +├── *.go # Additional Go code +└── Makefile # Build configuration +``` + +## Plugin System Architecture + +Dokku's functionality is entirely implemented through plugins. This architecture provides: + +- **Extensibility**: Add new features without modifying core code +- **Loose coupling**: Plugins communicate via well-defined triggers +- **Composability**: Mix and match builders, schedulers, and proxies + +### Plugin Communication via Triggers + +Plugins communicate through the **trigger system** powered by [plugn](https://github.com/dokku/plugn). When a trigger is fired, plugn executes matching scripts from all enabled plugins: + +``` ++---------------+ plugn trigger +---------------+ +| Plugin A | -------------------> | Plugin B | +| (fires event) | "post-deploy" | (listens for | ++---------------+ | event) | + +---------------+ + | + v + +---------------+ + | Plugin C | + | (also listens)| + +---------------+ +``` + +**Trigger execution flow:** + +1. A plugin calls `plugn trigger [args...]` +2. plugn searches `$PLUGIN_ENABLED_PATH/*/` for files named `` +3. Each matching executable is run with the provided arguments +4. Triggers can return data via stdout or signal errors via exit codes + +**Key trigger categories:** + +| Category | Examples | Purpose | +|----------|----------|---------| +| App lifecycle | `post-create`, `pre-delete`, `post-deploy` | React to app events | +| Build | `builder-detect`, `pre-build`, `builder-build` | Control build process | +| Deploy | `scheduler-deploy`, `check-deploy` | Manage deployments | +| Proxy | `proxy-build-config`, `nginx-pre-reload` | Configure reverse proxy | +| Git | `git-pre-pull`, `receive-app` | Handle git operations | + +For the complete list of triggers, see [Plugin Triggers](/docs/development/plugin-triggers.md). + +### Calling Triggers from Go Code + +Go-based plugins use the `common.CallPlugnTrigger` function: + +```go +result, err := common.CallPlugnTrigger(common.PlugnTriggerInput{ + Trigger: "post-deploy", + Args: []string{appName, port, ip, imageTag}, +}) +``` + +## Command Flow + +When a user runs a command like `dokku apps:list`, here's what happens: + +``` +User runs: ssh dokku@host apps:list + | + v ++-------------------+-------------------+ +| dokku bash script | +| 1. Source /etc/default/dokku | +| 2. Set DOKKU_ROOT, PLUGIN_PATH | +| 3. Source common/functions | +| 4. Call parse_args() | +| 5. Check user permissions | ++-------------------+-------------------+ + | + v ++-------------------+-------------------+ +| execute_dokku_cmd() | +| 1. Handle plugin aliases | +| 2. Check for subcommands/default | +| 3. Check for subcommands/ | +| 4. Fall back to commands scripts | ++-------------------+-------------------+ + | + v ++-------------------+-------------------+ +| plugins/apps/subcommands/list | +| (executes the actual command) | ++-------------------+-------------------+ +``` + +**Command resolution order:** + +1. Check `$PLUGIN_ENABLED_PATH//subcommands/default` for the default command +2. Check `$PLUGIN_ENABLED_PATH//subcommands/` for named commands +3. Iterate through all `$PLUGIN_ENABLED_PATH/*/commands` scripts as fallback + +## Deployment Pipeline + +When code is pushed via `git push dokku@host:myapp`, Dokku executes a multi-stage pipeline: + +``` ++-------------+ +-------------+ +-------------+ +-------------+ +| Receive |--->| Build |--->| Release |--->| Deploy | ++-------------+ +-------------+ +-------------+ +-------------+ + | | | | + v v v v + git-hook builder-detect builder-release scheduler-deploy + receive-app pre-build core-post-deploy + post-extract builder-build post-deploy +``` + +### Stage 1: Receive + +The git receive stage handles the incoming push and prepares the source code: + +1. `git-hook` receives the push and validates the branch +2. `receive-app` trigger is fired +3. Source code is extracted to a temporary directory +4. `post-extract` trigger allows modification of source + +### Stage 2: Build + +The build stage creates a Docker image from the source: + +1. `builder-detect` determines the builder type (herokuish, pack, dockerfile, etc.) +2. `pre-build` trigger runs pre-build hooks +3. `builder-build` creates the Docker image +4. `post-build` trigger runs post-build hooks + +### Stage 3: Release + +The release stage prepares the image for deployment: + +1. `pre-release-builder` allows image modifications +2. `builder-release` sets environment variables in the image +3. `post-release-builder` runs final release hooks + +### Stage 4: Deploy + +The deploy stage starts containers and configures networking: + +1. `scheduler-deploy` starts new containers +2. `check-deploy` runs health checks +3. `core-post-deploy` switches traffic to new containers +4. `post-deploy` runs deployment tasks +5. Old containers are retired + +## State Management + +Dokku uses a file-based state system for simplicity and transparency. + +### Property System + +Plugin-specific configuration is stored in the property system: + +``` +/var/lib/dokku/data/// +``` + +Properties are managed via helper functions: + +```bash +# Shell +fn-plugin-property-write "git" "$APP" "deploy-branch" "main" +fn-plugin-property-get "git" "$APP" "deploy-branch" +``` + +```go +// Go +common.PropertyWrite("git", appName, "deploy-branch", "main") +common.PropertyGet("git", appName, "deploy-branch") +``` + +### Per-App Data + +Application-specific data sometimes lives in `$DOKKU_ROOT//`: + +- `refs/` - Git references +- `tls/` - SSL certificates +- `ENV` - Environment file +- Container IDs, port mappings, etc. + +All non-git code is being migrated to the property system. + +### Global Configuration + +Global settings can be configured via: + +- `/etc/default/dokku` - System-level defaults +- `$DOKKU_ROOT/dokkurc` - User-level configuration +- `$DOKKU_ROOT/.dokkurc/*` - Additional configuration files + +## Key Design Decisions + +| Decision | Rationale | +|----------|-----------| +| **Plugin-based architecture** | Enables extensibility without modifying core code. Community plugins can add databases, caching, and other services. | +| **Bash + Go hybrid** | Bash for orchestration and simple commands; Go for performance-critical operations and complex logic. | +| **Trigger system** | Loose coupling between plugins. Plugins don't need to know about each other; they just fire and respond to events. | +| **File-based state** | Simple, transparent, and easy to debug. No database dependency. State can be inspected with standard Unix tools. | +| **Docker as foundation** | Leverages Docker's container runtime, networking, and image management. Allows multiple scheduler backends. | + +## Further Reading + +- [Plugin Creation](/docs/development/plugin-creation.md) - How to create custom plugins +- [Plugin Triggers](/docs/development/plugin-triggers.md) - Complete list of available triggers +- [Testing](/docs/development/testing.md) - How to test Dokku and plugins +- [Application Deployment](/docs/deployment/application-deployment.md) - User-facing deployment guide +- [Builder Management](/docs/deployment/builders/builder-management.md) - Available build backends +- [Scheduler Management](/docs/deployment/schedulers/scheduler-management.md) - Available scheduler backends diff --git a/docs/development/plugin-creation.md b/docs/development/plugin-creation.md index 6cbd9b6274e..73bd56565a9 100644 --- a/docs/development/plugin-creation.md +++ b/docs/development/plugin-creation.md @@ -10,7 +10,7 @@ When creating custom plugins: 2. Check out the [list of triggers](/docs/development/plugin-triggers.md) the plugin can implement 3. Upload the plugin to GitHub with a repository name following the `dokku-` convention (e.g. `dokku-mariadb`) 4. Edit [this page](/docs/community/plugins.md) and add a link to the plugin -5. Subscribe to the [dokku development blog](http://progrium.com) to be notified about API changes and releases +5. Subscribe to the [dokku development blog](https://dokku.com/blog/) to be notified about API changes and releases ### Compilable plugins (Golang, Java(?), C, etc.) @@ -103,7 +103,7 @@ if ! command -v "nginx" &>/dev/null; then fi # `which` example -if ! which nginx >/dev/null 2>&1; then +if ! which nginx &>/dev/null; then dokku_log_fail "Missing nginx, install it" fi ``` @@ -120,7 +120,7 @@ See the sample plugin below for an example. ### Namespace commands -All commands _should_ be namespaced. In cases where a core plugin is overriden, the plugin _may_ utilize the a namespace in use by the core, but generally this should be avoided to reduce confusion as to where the command is implemented. +All commands _should_ be namespaced. In cases where a core plugin is overridden, the plugin _may_ utilize the a namespace in use by the core, but generally this should be avoided to reduce confusion as to where the command is implemented. ### Implement a proper catch-all command @@ -145,6 +145,7 @@ Any functions that must be kept private should reside in the plugin's `trigger/` ### Use helper functions to fetch app images +> [!IMPORTANT] > New as of 0.4.0 Dokku allows image tagging and deployment of tagged images. This means hard-coding the `$IMAGE` as `dokku/$APP` is no longer sufficient. @@ -155,6 +156,7 @@ Plugins should use `get_running_image_tag()` and `get_app_image_name()` as sourc ### Use `$DOCKER_BIN` instead of `docker` directly +> [!IMPORTANT] > New as of 0.17.5 Certain systems may require a wrapper function around the `docker` binary for proper execution. Utilizing the `$DOCKER_BIN` environment variable when calling docker for those functions is preferred. @@ -169,6 +171,7 @@ docker run -d $IMAGE /bin/bash -e -c "$COMMAND" ### Include labels for all temporary containers and images +> [!IMPORTANT] > New as of 0.5.0 As of 0.5.0, labels are used to help cleanup intermediate containers with `dokku cleanup`. Plugins that create containers and images should add the correct labels to the `build`, `commit`, and `run` docker commands. @@ -197,7 +200,7 @@ Avoid copying files from running containers as these files may change over time. source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" local TMP_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") -trap "rm -rf '$TMP_FILE' >/dev/null" RETURN INT TERM +trap "rm -rf '$TMP_FILE' >/dev/null" RETURN INT TERM EXIT local IMAGE_TAG="$(get_running_image_tag "$APP")" local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") @@ -208,6 +211,7 @@ Files are copied from the `/app` directory - for images built via buildpacks - o ### Avoid calling the `dokku` binary directly +> [!IMPORTANT] > New as of 0.6.0 Plugins should **not** call the `dokku` binary directly from within plugins because clients using the `--app` argument are potentially broken when doing so. diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index c8906933809..cb9444be28b 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -61,7 +61,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x ### `app-exists` -- Description: Creates an app +- Description: Checks if an app exists - Invoked by: - Arguments: `$APP` - Example: @@ -74,6 +74,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `app-list` + +- Description: Lists all apps in available to the currently logged in user. Optionally disables filtering by user if the first argument is `false`. +- Invoked by: +- Arguments: `$FILTER` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `app-json-process-deploy-parallelism` - Description: Decides the parallelism to use when deploying a given process type. The default is 1 process entry at a type. @@ -89,6 +104,36 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `app-json-get-content` + +- Description: Outputs the contents of the app-json file, if any +- Invoked by: Deployment checks +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `app-json-is-valid` + +- Description: Checks to see if the provided app.json file is valid +- Invoked by: Appjson extraction during deployment +- Arguments: `$APP $APP_JSON_PATH` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `app-maybe-create` - Description: Creates an app (gated by whether this is globally enabled or not) @@ -158,6 +203,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `builder-create-dokku-image` + +- Description: Allows modification of the configured dokku-image +- Invoked by: `dokku deploy` +- Arguments: `$BUILDER_TYPE` `$APP` `$SOURCECODE_WORK_DIR` `$DOKKU_IMAGE` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `builder-detect` - Description: Allows overriding the auto-detected `herokuish` builder in favor of a custom one. Dockerfile gets lowest builder precedence. @@ -176,9 +236,9 @@ if [[ -f "$SOURCECODE_WORK_DIR/project.toml" ]]; then fi ``` -### `builder-create-dokku-image` +### `builder-dokku-image` -- Description: Allows modification of the configured dokku-image +- Description: Allows modification of the used dokku-image. - Invoked by: `dokku deploy` - Arguments: `$BUILDER_TYPE` `$APP` `$SOURCECODE_WORK_DIR` `$DOKKU_IMAGE` - Example: @@ -191,11 +251,57 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `builder-dokku-image` +### `builder-get-property` -- Description: Allows modification of the used dokku-image. -- Invoked by: `dokku deploy` -- Arguments: `$BUILDER_TYPE` `$APP` `$SOURCECODE_WORK_DIR` `$DOKKU_IMAGE` +- Description: Return the value for an app's builder property +- Invoked by: +- Arguments: `$APP $KEY` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROPERTY="$2" + +# TODO +``` + +### `builder-herokuish-allowed` + +- Description: Checks if herokuish is allowed to run on non-amd64 hosts +- Invoked by: unit tests +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `builder-image-is-cnb` + +- Description: Checks if an image is cnb-based +- Invoked by: unit tests +- Arguments: `$APP` `$IMAGE_TAG` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `builder-image-is-herokuish` + +- Description: Checks if an image is herokuish-based +- Invoked by: unit tests +- Arguments: `$APP` `$IMAGE_TAG` - Example: ```shell @@ -221,6 +327,126 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `buildpack-stack-name` + +- Description: Retrieves the configured buildpack stack for the pack and herokuish builders +- Invoked by: builder-herokuish and builder-pack +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `caddy-template-source` + +- Description: Retrieves an alternative template for the caddy compose config +- Invoked by: caddy-vhosts +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `certs-exists` + +- Description: Echos `true` if certs exists for the app, `false` otherwise +- Invoked by: +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `certs-force` + +- Description: Echos `true` if a cert should be simulated for the app, no output otherwise +- Invoked by: +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `certs-get` + +- Description: Returns the certificate or key content for an app +- Invoked by: `scheduler-k3s` +- Arguments: `$APP $KEY_TYPE` +- Example: + +```shell +#!/usr/bin/env bash +# Returns the certificate or key content for an app +# KEY_TYPE should be "crt" or "key" + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +APP="$1"; KEY_TYPE="$2" +APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" + +if [[ "$KEY_TYPE" == "crt" ]]; then + cat "$APP_SSL_PATH/server.crt" +elif [[ "$KEY_TYPE" == "key" ]]; then + cat "$APP_SSL_PATH/server.key" +fi +``` + +### `certs-remove` + +- Description: Removes the SSL cert/key pair from an app and fires the `post-certs-remove` and `post-domains-update` triggers. Fails if no app-specific SSL endpoint is defined. +- Invoked by: +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash +# Removes the SSL endpoint for an app + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +APP="$1" + +dokku certs:remove "$APP" +``` + +### `certs-set` + +- Description: Installs an SSL cert/key pair onto an app and fires the `post-certs-update` and `post-domains-update` triggers. `$CRT_FILE` and `$KEY_FILE` must be paths to readable PEM-encoded files. +- Invoked by: +- Arguments: `$APP $CRT_FILE $KEY_FILE` +- Example: + +```shell +#!/usr/bin/env bash +# Installs a cert/key pair for an app + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +APP="$1"; CRT_FILE="$2"; KEY_FILE="$3" + +dokku certs:add "$APP" "$CRT_FILE" "$KEY_FILE" +``` + ### `check-deploy` - Description: Allows you to run checks on a deploy before Dokku allows the container to handle requests. @@ -247,6 +473,22 @@ if [[ "$DOKKU_DISABLE_DEPLOY" = "true" ]]; then fi ``` +### `checks-get-property` + +- Description: Return the value for an app's checks property +- Invoked by: +- Arguments: `$APP $KEY` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROPERTY="$2" + +# TODO +``` + ### `commands help` and `commands :help` - Description: Your plugin should implement a `help` command in your `commands` file to take advantage of this plugin trigger. `commands help` is used by `dokku help` to aggregate all plugins abbreviated `help` output. Implementing `:help` in your `commands` file gives users looking for help, a more detailed output. 'commands help' must be implemented inside the `commands` plugin file. It's recommended that `PLUGIN_NAME:help` be added to the commands file to ensure consistency among community plugins and give you a new avenue to share rich help content with your user. @@ -339,6 +581,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `config-set` + +- Description: Sets one or more config values for an app without restarting (when --no-restart flag is specified) +- Invoked by: app-json plugin +- Arguments: `[--no-restart] $APP $KEY1=VALUE1 [$KEY2=VALUE2 ...]` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `core-post-deploy` > To avoid issues with community plugins, this plugin trigger should be used _only_ for core plugins. Please avoid using this trigger in your own plugins. @@ -382,25 +639,25 @@ echo "clock: some-command" >> Procfile popd &>/dev/null ``` -### `cron-write` +### `cron-get-property` -- Description: Force triggers writing out cron entries +- Description: Return the value for an app's cron property - Invoked by: -- Arguments: +- Arguments: `$APP $KEY` - Example: ```shell #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; PROPERTY="$2" # TODO ``` ### `cron-entries` -- Description: Allows injecting cron entries into the written out scheduled cron task list. Each entry is newline delimited, and individual entries come in the form `$SCHEDULE;$FULL_COMMAND;$ARBITRARY_DATA`. Individual implementations of cron writing can decide whether and how to include these cron entries. The `ARBITRARY_DATA` includes the log file path for the basic `docker-local` cron implementation. +- Description: Allows injecting cron tasks into the written out scheduled cron task list. Each entry is newline delimited, and individual tasks come in the form `$SCHEDULE;$FULL_COMMAND;$ARBITRARY_DATA`. Individual implementations of cron writing can decide whether and how to include these cron tasks. The `ARBITRARY_DATA` includes the log file path for the basic `docker-local` cron implementation. - Invoked by: - Arguments: `$DOKKU_SCHEDULER` - Example: @@ -426,7 +683,6 @@ DOKKU_SCHEDULER="$1" ```shell #!/usr/bin/env bash # Installs nginx for the current plugin -# Supports both opensuse and ubuntu set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x @@ -436,9 +692,8 @@ case "$DOKKU_DISTRO" in debian|raspbian|ubuntu) apt-get -qq -y --no-install-recommends install nginx ;; - - opensuse) - zypper -q in -y nginx + *) + echo "Installation on $DOKKU_DISTRO not supported" 1>&2 ;; esac ``` @@ -509,9 +764,25 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x echo 'derp.dkr.ecr.us-east-1.amazonaws.com' ``` +### `deploy-source-set` + +- Description: Used to set metadata about how the app is being deployed +- Invoked by: `git:from-archive`, `git:from-image`, `git:load-image`, `git:sync`, and all git push commands +- Arguments: `$APP $SOURCE_TYPE $METADATA` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `docker-args-build` -> Warning: Deprecated, please use `docker-args-process-build` instead +> [!WARNING] +> Deprecated, please use `docker-args-process-build` instead - Description: - Invoked by: `internal function dokku_build() (build phase)` @@ -536,7 +807,8 @@ echo -n "$STDIN$output" ### `docker-args-deploy` -> Warning: Deprecated, please use `docker-args-process-deploy` instead +> [!WARNING] +> Deprecated, please use `docker-args-process-deploy` instead - Description: - Invoked by: `dokku deploy` @@ -573,7 +845,7 @@ APP="$1"; IMAGE_SOURCE_TYPE="$2" ### `docker-args-process-deploy` -- Description: `$PROC_TYPE` may be set to magic `_all_` process type to signify global docker deploy options. +- Description: emits docker arguments scoped to a specific Procfile process type. The `docker-options` plugin implements this trigger to surface options registered via `docker-options:add --process `. `$PROC_TYPE` may be empty (or set to the magic `_default_` value) to signify default-scope docker deploy options. - Invoked by: `dokku deploy` - Arguments: `$APP $IMAGE_SOURCE_TYPE $IMAGE_TAG [$PROC_TYPE $CONTAINER_INDEX]` - Example: @@ -607,7 +879,8 @@ APP="$1"; IMAGE_SOURCE_TYPE="$3"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $AP ### `docker-args-run` -> Warning: Deprecated, please use `docker-args-process-run` instead +> [!WARNING] +> Deprecated, please use `docker-args-process-run` instead - Description: - Invoked by: `dokku run` @@ -684,11 +957,11 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `domains-setup` +### `domains-urls` -- Description: Initializes domains for an app if enabled +- Description: Gets an app's url(s) - Invoked by: -- Arguments: `$APP` +- Arguments: `$APP $URL_TYPE` - Example: ```shell @@ -729,6 +1002,22 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `git-get-property` + +- Description: Return the value for an app's git property +- Invoked by: +- Arguments: `$APP $KEY` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROPERTY="$2" + +# TODO +``` + ### `git-from-archive` - Description: Updates an app's git repository from an archive and then triggers a build @@ -774,6 +1063,20 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `git-has-code` + +- Description: Checks to see if there is code at the specified branch +- Arguments: `$APP $BRANCH_NAME` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `git-post-pull` - Description: @@ -804,7 +1107,8 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -> Warning: The `git-pre-pull` trigger should _not_ be used for authentication since it does not get called for commands that use `git-upload-archive` such as `git archive`. Instead, use the [`user-auth`](#user-auth) trigger. +> [!WARNING] +> The `git-pre-pull` trigger should _not_ be used for authentication since it does not get called for commands that use `git-upload-archive` such as `git archive`. Instead, use the [`user-auth`](#user-auth) trigger. ### `git-revision` @@ -840,41 +1144,41 @@ if [[ ! -f "$DOKKU_ROOT/VHOST" ]]; then fi ``` -### `logs-get-property` +### `haproxy-template-source` -- Description: Fetches a given logs property value -- Invoked by: -- Arguments: `$APP` `$PROPERTY` +- Description: Retrieves an alternative template for the haproxy compose config +- Invoked by: haproxy-vhosts +- Arguments: `$APP` - Example: ```shell #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -APP="$1"; PROPERTY="$2" # TODO ``` -### `network-build-config` +### `logs-get-property` -- Description: Rebuilds network configuration -- Invoked by: `internally triggered by proxy-build-config within proxy implementations` -- Arguments: `$APP` +- Description: Return the value for an app's log property +- Invoked by: +- Arguments: `$APP $KEY` - Example: ```shell #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROPERTY="$2" # TODO ``` -### `network-clear-config` +### `network-build-config` -- Description: Clears network configuration -- Invoked by: `internally triggered within proxy implementations` +- Description: Rebuilds network configuration +- Invoked by: `internally triggered by proxy-build-config within proxy implementations` - Arguments: `$APP` - Example: @@ -886,11 +1190,11 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `network-compute-ports` +### `network-clear-config` -- Description: Computes the ports for a given app container -- Invoked by: `internally triggered by proxy-build-config within proxy implementations` -- Arguments: `$APP $PROC_TYPE $IS_HEROKUISH_CONTAINER $CONTAINER_INDEX` +- Description: Clears network configuration +- Invoked by: `internally triggered within proxy implementations` +- Arguments: `$APP` - Example: ```shell @@ -946,24 +1250,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `network-get-port` - -- Description: Return the port for a given app container -- Invoked by: `internally triggered by a deploy` -- Arguments: `$APP $PROCESS_TYPE $CONTAINER_ID $IS_HEROKUISH_CONTAINER` -- Example: - -```shell -#!/usr/bin/env bash - -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -# TODO -``` - ### `network-get-property` -- Description: Return the network value for an app's property +- Description: Return the value for an app's network property - Invoked by: `internally triggered by a deploy` - Arguments: `$APP $KEY` - Example: @@ -1026,7 +1315,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - Description: Return the path to a `sigil` template that should be used to generate a given nginx configuration file. - Invoked by: `nginx-vhosts#build-config` - Arguments: `$APP $TEMPLATE_TYPE` - - The `TEMPLATE_TYPE` argument can be one of: `[app-config, hsts-config, validate-config]` + - The `TEMPLATE_TYPE` argument can be one of: `[app-config, hsts-config, validate-config]` - Example: ```shell @@ -1051,6 +1340,8 @@ esac The default templates are viewable here: [plugins/nginx-vhosts/templates/](https://github.com/dokku/dokku/tree/master/plugins/nginx-vhosts/templates) +Overriding the `validate-config` template is the supported way to make deploy-time pre-validation aware of directives from nginx modules that the host loads via `load_module` in its global config. As implementing this trigger requires a [custom plugin](/docs/development/plugin-creation.md), see [Custom nginx modules](/docs/appendices/file-formats/nginx-conf-sigil.md#custom-nginx-modules) for a complete example. + ### `nginx-dokku-template-source` - Description: Return the path to a `sigil` template that should be used to generate the `dokku.conf` nginx configuration file. @@ -1089,10 +1380,11 @@ echo "$NEW_SUBDOMAIN.$VHOST" ### `nginx-pre-reload` -> Warning: The arguments INTERNAL_PORT and INTERNAL_IP_ADDRESS are no longer sufficient to retrieve all app listeners. Please run `plugn trigger network-get-listeners APP` within any implementation of `nginx-pre-reload` in order to retrieve all application listeners. +> [!WARNING] +> The arguments INTERNAL_PORT and INTERNAL_IP_ADDRESS are no longer sufficient to retrieve all app listeners. Please run `plugn trigger network-get-listeners APP` within any implementation of `nginx-pre-reload` in order to retrieve all application listeners. - Description: Run before nginx reloads hosts -- Invoked by: `dokku nginx:build-config` +- Invoked by: `dokku proxy:build-config` - Arguments: `$APP $INTERNAL_PORT $INTERNAL_IP_ADDRESS` - Example: @@ -1106,6 +1398,112 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x nginx -t ``` +### `openresty-template-source` + +- Description: Retrieves an alternative template for the openresty compose config +- Invoked by: openresty-vhosts +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-clear` + +- Description: Clears the ports for a given app without triggering further restarts or rebuilds +- Invoked by: internally +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-configure` + +- Description: Configures the initial proxy ports +- Invoked by: `internally triggered by proxy plugins` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-get` + +- Description: Returns a list of port mappings, newline delimited +- Invoked by: Various networking plugins +- Arguments: `$APP $FORMAT` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-get-available` + +- Description: Prints out an available port greater than 1024 +- Invoked by: Various networking plugins +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `ports-get-property` + +- Description: Return the value for an app's ports property +- Invoked by: +- Arguments: `$APP $KEY` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROPERTY="$2" + +# TODO +``` + +### `ports-set-detected` + +- Description: Allows builders to specify detected port mappings for a given app +- Invoked by: Builder plugins +- Arguments: `$APP [$PORT_MAPPING...]` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `post-app-clone` - Description: Allows you to run commands after an app was cloned. @@ -1167,11 +1565,11 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x ``` -### `post-build-buildpack` +### `post-build` -- Description: Allows you to run commands after the build image is create for a given app. Only applies to apps using buildpacks. +- Description: Allows you to run commands after the build image is create for a given app. - Invoked by: `internal function dokku_build() (build phase)` -- Arguments: `$APP` `$SOURCECODE_WORK_DIR` +- Arguments: `$BUILDER_TYPE $APP $SOURCECODE_WORK_DIR` - Example: ```shell @@ -1182,12 +1580,12 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `post-build-pack` +### `post-build-buildpack` -> Warning: The pack plugin trigger apis are under development and may change -> between minor releases until the 1.0 release. +> [!WARNING] +> Deprecated, please use `post-build` instead -- Description: Allows you to run commands after the build image is create for a given app. Only applies to apps using pack. +- Description: Allows you to run commands after the build image is create for a given app. Only applies to apps using buildpacks. - Invoked by: `internal function dokku_build() (build phase)` - Arguments: `$APP` `$SOURCECODE_WORK_DIR` - Example: @@ -1202,6 +1600,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x ### `post-build-dockerfile` +> [!WARNING] +> Deprecated, please use `post-build` instead + - Description: Allows you to run commands after the build image is create for a given app. Only applies to apps using a dockerfile. - Invoked by: `internal function dokku_build() (build phase)` - Arguments: `$APP` @@ -1215,6 +1616,42 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `post-build-lambda` + +> [!WARNING] +> Deprecated, please use `post-build` instead + +- Description: Allows you to run commands after the build image is create for a given app. Only applies to apps using lambda. +- Invoked by: `internal function dokku_build() (build phase)` +- Arguments: `$APP` `$SOURCECODE_WORK_DIR` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `post-build-pack` + +> [!WARNING] +> Deprecated, please use `post-build` instead + +- Description: Allows you to run commands after the build image is create for a given app. Only applies to apps using pack. +- Invoked by: `internal function dokku_build() (build phase)` +- Arguments: `$APP` `$SOURCECODE_WORK_DIR` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `post-certs-remove` - Description: Allows you to run commands after a cert is removed @@ -1251,7 +1688,7 @@ APP="$1" ### `post-config-update` -- Description: Allows you to get notified when one or more configs is added or removed. Action can be `set` or `unset`. +- Description: Allows you to get notified when one or more configs is added or removed. Action can be `set` or `unset`. - Invoked by: `dokku config:set`, `dokku config:unset` - Arguments: `$APP` `set|unset` `key1=VALUE1 key2=VALUE2` - Example: @@ -1266,9 +1703,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x ### `post-container-create` -- Description: This trigger should be used to do stuff to containers after they are created but before they are started. They are explicitely for commands that may involve network traffic, and _not_ for commands that are self-contained, such as chown or tar. +- Description: This trigger should be used to do stuff to containers after they are created but before they are started. They are explicitly for commands that may involve network traffic, and _not_ for commands that are self-contained, such as chown or tar. - Invoked by: `dokku run`, `dokku ps:rebuild`, `dokku deploy` -- Arguments "app|service" "$CONTAINER_ID" "$APP|$SERVICE" "$PHASE" +- Arguments: "app|service" "$CONTAINER_ID" "$APP|$SERVICE" "$PHASE" ```shell #!/usr/bin/env bash @@ -1339,7 +1776,7 @@ curl "http://httpstat.us/200" ### `post-domains-update` - Description: Allows you to run commands once the domain for an app has been updated. It also sends in the command that has been used. This can be "add", "clear" or "remove". The third argument will be the optional list of domains -- Invoked by: `dokku domains:add`, `dokku domains:clear`, `dokku domains:remove`, `dokku domains:set` +- Invoked by: `dokku domains:add`, `dokku domains:clear`, `dokku domains:remove`, `dokku domains:reset`, `dokku domains:set` - Arguments: `$APP` `action name` `domains` - Example: @@ -1378,8 +1815,8 @@ popd &>/dev/null ### `post-proxy-ports-update` -- Description: Allows you to run commands once the proxy port mappings for an app have been updated. It also sends the invoking command. This can be "add", "clear" or "remove". -- Invoked by: `dokku proxy:ports-add`, `dokku proxy:ports-clear`, `dokku proxy:ports-remove` +- Description: Allows you to run commands once the port mappings for an app have been updated. It also sends the invoking command. This can be "add", "clear" or "remove". +- Invoked by: `dokku ports:add`, `dokku ports:clear`, `dokku ports:remove` - Arguments: `$APP` `action name` - Example: @@ -1396,9 +1833,24 @@ APP="$1" haproxy-build-config "$APP" ``` +### `post-registry-login` + +- Description: Allows running a command after the a registry has been logged into +- Invoked by: `dokku registry:login` +- Arguments: `$DOCKER_REGISTRY_SERVER $DOCKER_REGISTRY_USER` +- Example: + +```shell +#!/usr/bin/env bash + +# the DOCKER_REGISTRY_PASS env var is also set +echo "$DOCKER_REGISTRY_PASS" +``` + ### `post-release-builder` -> Warning: Image mutation in this trigger may result in an invalid run state, and is heavily discouraged. +> [!WARNING] +> Image mutation in this trigger may result in an invalid run state, and is heavily discouraged. - Description: Invokes a command after the build process is complete. - Invoked by: builder plugins @@ -1415,94 +1867,80 @@ BUILDER_TYPE="$1"; APP="$2"; IMAGE=$3 # TODO ``` -### `post-release-buildpack` - -> Warning: Deprecated, please use `post-release-builder` instead -> Warning: Image mutation in this trigger may result in an invalid run state, and is heavily discouraged. +### `post-stack-set` TODO -- Description: Allows you to run commands after environment variables are set for the release step of the deploy. Only applies to apps using buildpacks. -- Invoked by: `internal function dokku_release() (release phase)` -- Arguments: `$APP $IMAGE_TAG` +- Description: Allows you to run commands after changing the stack +- Invoked by: buildpacks plugin +- Arguments: `$APP` `$VALUE` - Example: ```shell #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) # TODO ``` -### `post-release-pack` - -> Warning: Deprecated, please use `post-release-builder` instead - -> Warning: The pack plugin trigger apis are under development and may change -> between minor releases until the 1.0 release. - -> Warning: Image mutation in this trigger may result in an invalid run state, and is heavily discouraged. +### `post-stop` -- Description: Allows you to run commands after environment variables are set for the release step of the deploy. Only applies to apps using pack. -- Invoked by: `internal function dokku_release() (release phase)` -- Arguments: `$APP $IMAGE_TAG` +- Description: Can be used to run commands after an app is manually stopped +- Invoked by: `dokku ps:stop` +- Arguments: `$APP` - Example: ```shell #!/usr/bin/env bash +# Marks an app as manually stopped set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) - -# TODO -``` -### `post-release-dockerfile` +APP="$1"; -> Warning: Deprecated, please use `post-release-builder` instead +dokku config:set --no-restart $APP MANUALLY_STOPPED=1 +``` -> Warning: Image mutation in this trigger may result in an invalid run state, and is heavily discouraged. +### `pre-build` -- Description: Allows you to run commands after environment variables are set for the release step of the deploy. Only applies to apps using a dockerfile. -- Invoked by: `internal function dokku_release() (release phase)` -- Arguments: `$APP $IMAGE_TAG` +- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. +- Invoked by: `internal function dokku_build() (build phase)` +- Arguments: `$BUILDER_TYPE $APP $SOURCECODE_WORK_DIR` - Example: ```shell #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) # TODO ``` -### `post-stop` +### `pre-build-buildpack` -- Description: Can be used to run commands after an app is manually stopped -- Invoked by: `dokku ps:stop` -- Arguments: `$APP` +> [!WARNING] +> Deprecated, please use `pre-build` instead + +- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using buildpacks. +- Invoked by: `internal function dokku_build() (build phase)` +- Arguments: `$APP` `$SOURCECODE_WORK_DIR` - Example: ```shell #!/usr/bin/env bash -# Marks an app as manually stopped set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -APP="$1"; - -dokku config:set --no-restart $APP MANUALLY_STOPPED=1 +# TODO ``` -### `pre-build-buildpack` +### `pre-build-dockerfile` -- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using buildpacks. +> [!WARNING] +> Deprecated, please use `pre-build` instead + +- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using a dockerfile. - Invoked by: `internal function dokku_build() (build phase)` -- Arguments: `$APP` `$SOURCECODE_WORK_DIR` +- Arguments: `$APP` - Example: ```shell @@ -1513,12 +1951,12 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `pre-build-pack` +### `pre-build-lambda` -> Warning: The pack plugin trigger apis are under development and may change -> between minor releases until the 1.0 release. +> [!WARNING] +> Deprecated, please use `pre-build` instead -- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using pack. +- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using lambda. - Invoked by: `internal function dokku_build() (build phase)` - Arguments: `$APP` `$SOURCECODE_WORK_DIR` - Example: @@ -1531,11 +1969,14 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `pre-build-dockerfile` +### `pre-build-pack` -- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using a dockerfile. +> [!WARNING] +> Deprecated, please use `pre-build` instead + +- Description: Allows you to run commands before the build image is created for a given app. For instance, this can be useful to add env vars to your container. Only applies to apps using pack. - Invoked by: `internal function dokku_build() (build phase)` -- Arguments: `$APP` +- Arguments: `$APP` `$SOURCECODE_WORK_DIR` - Example: ```shell @@ -1569,6 +2010,9 @@ fi ### `pre-deploy` +> [!WARNING] +> Deprecated, please use `pre-release-builder` instead + - Description: Allows the running of code before the app's processes are scaled up and after the docker images are prepared. - Invoked by: `dokku deploy` - Arguments: `$APP $IMAGE_TAG` @@ -1642,8 +2086,28 @@ APP="$1"; IMAGE_SOURCE_TYPE="$2"; TMP_WORK_DIR="$3"; REV="$4" echo "$APP" > "$TMP_WORK_DIR/dokku-is-awesome" ``` +### `pre-release-builder` + +- Description: Allows you to run commands before environment variables are set for the release step of the deploy. +- Invoked by: `internal function dokku_release() (release phase)` +- Arguments: `$BUILDER_TYPE $APP $IMAGE` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +BUILDER_TYPE="$1"; APP="$2"; IMAGE_TAG="$3"; + +# TODO +``` + ### `pre-release-buildpack` +> [!WARNING] +> Deprecated, please use `pre-release-builder` instead + - Description: Allows you to run commands before environment variables are set for the release step of the deploy. Only applies to apps using buildpacks. - Invoked by: `internal function dokku_release() (release phase)` - Arguments: `$APP $IMAGE_TAG` @@ -1660,19 +2124,41 @@ APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) dokku_log_info1 "Installing GraphicsMagick..." CMD="cat > gm && \ - dpkg -s graphicsmagick >/dev/null 2>&1 || \ + dpkg -s graphicsmagick &>/dev/null || \ (apt-get update -qq && apt-get -qq -y --no-install-recommends install graphicsmagick && apt-get clean)" -CID=$(docker run $DOKKU_GLOBAL_RUN_ARGS -i -a stdin $IMAGE /bin/bash -c "$CMD") +CID_FILE=/tmp/cid-file +docker run $DOKKU_GLOBAL_RUN_ARGS -i -a stdin --cidfile /tmp/cid-file "$IMAGE" /bin/bash -c "$CMD" +CID="$(cat "$CID_FILE")" test $(docker wait $CID) -eq 0 DOCKER_COMMIT_LABEL_ARGS=("--change" "LABEL org.label-schema.schema-version=1.0" "--change" "LABEL org.label-schema.vendor=dokku" "--change" "LABEL com.dokku.app-name=$APP") docker commit "${DOCKER_COMMIT_LABEL_ARGS[@]}" $CID $IMAGE >/dev/null ``` +### `pre-release-lambda` + +> [!WARNING] +> Deprecated, please use `pre-release-builder` instead + +- Description: Allows you to run commands before environment variables are set for the release step of the deploy. Only applies to apps using lambda. +- Invoked by: `internal function dokku_release() (release phase)` +- Arguments: `$APP $IMAGE_TAG` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; IMAGE_TAG="$2"; + +# TODO +``` + ### `pre-release-pack` -> Warning: The pack plugin trigger apis are under development and may change -> between minor releases until the 1.0 release. +> [!WARNING] +> Deprecated, please use `pre-release-builder` instead - Description: Allows you to run commands before environment variables are set for the release step of the deploy. Only applies to apps using pack. - Invoked by: `internal function dokku_release() (release phase)` @@ -1691,6 +2177,9 @@ APP="$1"; IMAGE_TAG="$2"; ### `pre-release-dockerfile` +> [!WARNING] +> Deprecated, please use `pre-release-builder` instead + - Description: Allows you to run commands before environment variables are set for the release step of the deploy. Only applies to apps using a dockerfile. - Invoked by: `internal function dokku_release() (release phase)` - Arguments: `$APP $IMAGE_TAG` @@ -1710,7 +2199,7 @@ APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) - Description: Allows you to run commands before all containers are restored - Invoked by: `dokku ps:restore` -- Arguments: `$DOKKU_SCHEDULER $APP` +- Arguments: - Example: ```shell @@ -1740,21 +2229,6 @@ APP="$1"; curl "https://dokku.me/starting/${APP}" || true ``` -### `procfile-extract` - -- Description: Extracts a Procfile from a given image to a path -- Invoked by: `internally` -- Arguments: `$APP $IMAGE` -- Example: - -```shell -#!/usr/bin/env bash - -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -# TODO -``` - ### `procfile-get-command` - Description: Fetches the command for a specific process type @@ -1770,9 +2244,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `procfile-remove` +### `procfile-exists` -- Description: Removes the extracted Procfile +- Description: Checks if a procfile exists for the specified app - Invoked by: `internally` - Arguments: `$APP` - Example: @@ -1889,6 +2363,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` + ### `ps-current-scale` - Description: Prints out the current scale contents (process-type=quantity) delimited by newlines. @@ -2028,7 +2503,8 @@ mail -s "$APP containers on $HOSTNAME failed to retire" ops@dokku.me ### `scheduler-app-status` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Fetch the status of an app @@ -2045,9 +2521,30 @@ DOKKU_SCHEDULER="$1"; APP="$2"; # TODO ``` +### `scheduler-cron-write` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Force triggers writing out cron tasks. Arguments are optional. +- Invoked by: `ps:start`, `ps:stop`, `cron:set` +- Arguments: `$DOKKU_SCHEDULER $APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +# TODO +``` + ### `scheduler-deploy` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run scheduler commands when an app is deployed @@ -2066,7 +2563,8 @@ DOKKU_SCHEDULER="$1"; APP="$2"; IMAGE_TAG="$3"; ### `scheduler-detect` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to check which scheduler is in use for an app @@ -2085,7 +2583,8 @@ APP="$1" ### `scheduler-enter` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to enter a running container for a given app @@ -2104,7 +2603,8 @@ DOKKU_SCHEDULER="$1"; APP="$2"; ARGS="$@" ### `scheduler-inspect` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run inspect commands for all containers for a given app @@ -2121,9 +2621,30 @@ DOKKU_SCHEDULER="$1"; APP="$2"; # TODO ``` +### `scheduler-is-deployed` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to check if the scheduler has deployed the app +- Invoked by: Various plugins +- Arguments: `$DOKKU_SCHEDULER $APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; + +# TODO +``` + ### `scheduler-logs` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run scheduler commands when retrieving container logs @@ -2142,7 +2663,8 @@ DOKKU_SCHEDULER="$1"; APP="$2"; PROCESS_TYPE="$3"; TAIL="$4"; PRETTY_PRINT="$5"; ### `scheduler-logs-failed` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run scheduler commands when retrieving failed container logs @@ -2159,9 +2681,50 @@ DOKKU_SCHEDULER="$1"; APP="$2"; # TODO ``` +### `scheduler-proxy-logs` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run scheduler commands when retrieving failed container logs +- Invoked by: `dokku nginx:access-logs` and `dokku nginx:error-logs` +- Arguments: `$DOKKU_SCHEDULER $APP $PROXY_TYPE $LOG_TYPE $TAIL $NUM_LINES` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; $PROXY_TYPE="$3"; LOG_TYPE="$4"; TAIL="$5"; NUM_LINES="$6" + +# TODO +``` + +### `scheduler-pre-restore` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run commands before an app is restored +- Invoked by: `dokku ps:restore` +- Arguments: `$DOKU_SCHEDULER $APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; + +# TODO +``` + ### `scheduler-post-delete` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run scheduler commands when an app is deleted @@ -2178,9 +2741,30 @@ DOKKU_SCHEDULER="$1"; APP="$2"; IMAGE_TAG="$3"; # TODO ``` +### `scheduler-post-deploy-process` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run a command after a process has been started +- Invoked by: scheduler-docker-local +- Arguments: `$APP $PROCESS_TYPE` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROCESS_TYPE="$2"; + +# TODO +``` + ### `scheduler-post-run` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run scheduler commands after a `dokku run` invocation is called @@ -2199,7 +2783,8 @@ DOKKU_SCHEDULER="$1"; APP="$2"; CONTAINER_ID="$3"; ### `scheduler-register-retired` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows scheduling retiring a local container and any related images @@ -2219,7 +2804,8 @@ CONTAINER_ID="$2"; ### `scheduler-retire` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run scheduler commands when containers should be force retired from the system @@ -2238,7 +2824,8 @@ DOKKU_SCHEDULER="$1"; APP="$2"; ### `scheduler-run` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run scheduler commands when a command is executed for your app @@ -2255,9 +2842,90 @@ DOKKU_SCHEDULER="$1"; APP="$2"; ARGS="${@:3}"; # TODO ``` +### `scheduler-run-list` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Lists all run containers for a given app +- Invoked by: `dokku run` +- Arguments: `$DOKKU_SCHEDULER $APP $FORMAT` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; FORMAT="$3"; + +# TODO +``` + +### `scheduler-run-logs` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run scheduler commands when retrieving one-off container logs +- Invoked by: `dokku run` +- Arguments: `$DOKKU_SCHEDULER $APP $CONTAINER $TAIL $PRETTY_PRINT $NUM` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; CONTAINER="$3"; TAIL="$4"; PRETTY_PRINT="$5"; NUM="$6" + +# TODO +``` + +### `scheduler-run-retire` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to retire containers started by the `run` command +- Invoked by: `dokku run:retire` +- Arguments: `$DOKKU_SCHEDULER $APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; + +# TODO +``` + +### `scheduler-run-stop` + +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows a scheduler to stop all "run" containers or a specified container +- Invoked by: `dokku run:stop` +- Arguments: `$DOKKU_SCHEDULER $APP $CONTAINER_NAME` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; CONTAINER_NAME="$3"; + +# TODO +``` + ### `scheduler-stop` -> Warning: The scheduler plugin trigger apis are under development and may change +> [!WARNING] +> The scheduler plugin trigger apis are under development and may change > between minor releases until the 1.0 release. - Description: Allows you to run scheduler commands when a tag is destroyed @@ -2274,10 +2942,57 @@ DOKKU_SCHEDULER="$1"; APP="$2"; REMOVE_CONTAINERS="$3"; # TODO ``` -### `storage-list` +### `storage-list` (deprecated) + +> [!WARNING] +> Deprecated as of 0.38.0. The trigger handler still functions for back-compat +> with external plugins but emits a deprecation warning on every invocation. +> In-process callers should use the `storage-app-mounts` trigger instead, or +> the `storage` Go package directly. + +- Description: Returns a list of storage mounts for an app +- Invoked by: external plugins (the core `storage:list` subcommand no longer uses it) +- Arguments: `$APP $PHASE $FORMAT` + +### `storage-app-mounts` + +- Description: Returns the (entry, attachment) pairs an app has for the given phase, in JSON. Schedulers consume this to render volume mounts. +- Invoked by: scheduler plugins during deploy +- Arguments: `$APP $PHASE` +- Output: a JSON array of objects shaped `{"entry": Entry, "attachment": Attachment}` -- Description: Returns a list of storage mounts -- Invoked by: `dokku storage:list` and `dokku deploy` +### `storage-create` + +- Description: Provisions or updates the underlying volume for a k3s storage entry. Called when `storage:create` or `storage:set` runs against a k3s entry. Receives the JSON-encoded entry on stdin. +- Invoked by: `dokku storage:create`, `dokku storage:set` +- Arguments: `$ENTRY_NAME` +- Stdin: JSON-encoded `Entry` payload + +### `storage-destroy` + +- Description: Releases the underlying volume for a k3s storage entry. Called when `storage:destroy` runs against a k3s entry. Receives the JSON-encoded entry on stdin. +- Invoked by: `dokku storage:destroy` +- Arguments: `$ENTRY_NAME` +- Stdin: JSON-encoded `Entry` payload + +### `storage-status` + +- Description: Returns the current status of a storage entry's underlying volume. Used by `storage:wait` to poll the cluster. The handler should print one line: `Bound`, `Pending`, or `Lost` for k3s entries. +- Invoked by: `dokku storage:wait` +- Arguments: `$ENTRY_NAME` +- Stdin: JSON-encoded `Entry` payload + +### `scheduler-storage-exec` + +- Description: Runs an interactive or non-interactive command against a storage entry. The storage plugin fires this with `` as the first arg; each scheduler plugin's handler matches against its own scheduler name and either handles or no-ops, mirroring `scheduler-deploy` / `scheduler-app-status`. Plugn forwards stdin/stdout/stderr to the handler subprocess so an interactive shell streams cleanly. The handler exits with the underlying tool's status code so `dokku storage:exec` propagates exit codes verbatim. +- Invoked by: `dokku storage:exec` +- Arguments: `$SCHEDULER $ENTRY_NAME $IMAGE [-- $cmd...]` +- Flags: `--interactive` (stdin is open), `--tty` (stdin is a terminal), `--as-user ` (override `entry.Chown`). + +### `traefik-template-source` + +- Description: Retrieves an alternative template for the traefik compose config +- Invoked by: traefik-vhosts - Arguments: `$APP` - Example: @@ -2285,7 +3000,6 @@ DOKKU_SCHEDULER="$1"; APP="$2"; REMOVE_CONTAINERS="$3"; #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -APP="$1" # TODO ``` @@ -2367,3 +3081,59 @@ shift 2 [[ "$SSH_NAME" == "default" && $1 == plugin:* ]] && exit 1 exit 0 ``` + +### `user-auth-app` + +This is a special plugin trigger that is executed when listing apps or checking if an app exists. All Dokku commands should check if an app exists at least once before interacting with them so as not to circumvent the check. + +Note that the trigger should exit `0`, and each non-empty line on stdout is captured as a valid app name. + +The `SSH_USER` is the original ssh user. If you are running remote commands, this user will typically be `dokku`, and as such should not be trusted when checking permissions. If you are connected via ssh as a different user who then invokes `dokku`, the value of this variable will be that user's name (`root`, `myuser`, etc.). + +The `SSH_NAME` is the `NAME` variable set via the `sshcommand acl-add` command. For reference, the following command can be run as the root user to specify a specific `NAME` for a given ssh key: + +```shell +sshcommand acl-add dokku NAME < $PATH_TO_SSH_KEY +``` + +Note that the `NAME` value is set at the first ssh key match. If an ssh key is set in the `/home/dokku/.ssh/authorized_keys` multiple times, the first match will decide the value. + +- Description: Allows you to deny access to a Dokku app by either ssh user or associated ssh-command NAME user. +- Invoked by: `dokku` +- Arguments: `$SSH_USER $SSH_NAME $DOKKU_COMMAND` +- Example: + +```shell +#!/usr/bin/env bash +# hide any apps with the prefix "admin" +# if the logged in user (SSH_USER) or SSH_NAME is not `root` + +main() { + declare SSH_USER="$1" SSH_NAME="$2" ARGS=("${@:3}") + + for arg in "${ARGS[@]}"; do + if [[ "$arg" == admin-* ]] && [[ "$SSH_USER" != "root" ]] && [[ "$SSH_NAME" != "root" ]]; then + continue + fi + + echo "${arg}" + done +} + +main "$@" +``` + +### `vector-template-source` + +- Description: Retrieves an alternative template for the vector compose config +- Invoked by: caddy-vhosts +- Arguments: +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` diff --git a/docs/development/release-process.md b/docs/development/release-process.md index b82cfe1d2c4..b81ffeabc05 100644 --- a/docs/development/release-process.md +++ b/docs/development/release-process.md @@ -29,7 +29,7 @@ The workflow looks like this: ```shell # having dokku-arch in ../dokku-arch vagrant up build-arch -# wait for "==> build-arch: ==> Finished making: dokku 0.26.8-2 (Mon Feb 22 23:20:37 CET 2016)" +# wait for "==> build-arch: ==> Finished making: dokku 0.38.25-2 (Mon Feb 22 23:20:37 CET 2016)" cd ../dokku-arch git add PKGBUILD .SRCINFO git commit -m 'Update to dokku 0.9.9' diff --git a/docs/development/testing.md b/docs/development/testing.md index fc32963a015..1daaca2be9f 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -9,14 +9,16 @@ We maintain the Dokku test harness within the `tests` directory: ## Continuous Integration -All pull requests have tests run against them on [GitHub Actions](https://github.com/features/actions), a continuous integration platform that provides Docker support for Ubuntu Trusty 18.04. +All pull requests have tests run against them on [GitHub Actions](https://github.com/features/actions), a continuous integration platform that provides Docker support for Ubuntu Noble 24.04. If you wish to skip tests for a particular commit, e.g. documentation changes, you may add the `[ci skip]` designator to your commit message. Commits that _should_ be tested but have the above designator will not be merged. -While we do provide official packages for a variety of platforms, as our test suite currently runs on Ubuntu Trusty 18.04, we only provide official installation support for that platform and the latest LTS release of Ubuntu (currently 20.04). +While we do provide official packages for a variety of platforms, as our test suite currently runs on Ubuntu Noble 24.04, we only provide official installation support for that platform and the supported LTS releases of Ubuntu (currently 22.04, and 24.04). ## Local Test Execution +### Vagrant VM + - Setup Dokku in a [Vagrant VM](/docs/getting-started/install/vagrant.md). - Run the following to setup tests and execute them: @@ -25,18 +27,25 @@ While we do provide official packages for a variety of platforms, as our test su sudo su - cd ~/dokku make ci-dependencies setup-deploy-tests + ``` + +After making changes to your local Dokku clone, don't forget to update the Vagrant Dokku install. + +```shell +# update vagrant dokku install from local git clone +make copyfiles - # execute the entire test suite (linter, bats tests, and app deployment tests) - make test +# build a specific plugin +make go-build-plugin copyplugin PLUGIN_NAME=apps +``` - # run linter - make lint +### VSCode Dev Container - # execute all bats tests - make unit-tests +- Open Dokku in a VSCode DevContainer +- Run the following in the VSCode terminal to setup tests and execute them: - # execute all app deployment tests - make deploy-tests + ```shell + make ci-dependencies setup-deploy-tests ``` After making changes to your local Dokku clone, don't forget to update the Vagrant Dokku install. @@ -49,7 +58,35 @@ make copyfiles make go-build-plugin copyplugin PLUGIN_NAME=apps ``` -Additionally you may run a specific app deployment tests with a target similar to: +### Executing tests + +Execute the entire test suite (linter, bats tests, and app deployment tests): + +```shell +make test +``` + +Run the linter + +```shell +make lint +``` + +Execute all bats tests + +```shell +make unit-tests +``` + +Execute all app deployment tests + +```shell +make deploy-tests +``` + +#### Executing App Tests + +You may run a specific app deployment tests with a target similar to: ```shell make deploy-test-nodejs-express @@ -57,32 +94,32 @@ make deploy-test-nodejs-express For a full list of test make targets check out `tests.mk` in the root of the Dokku repository. -## Executing a single test suite +#### Executing a single test suite When working on a particular plugin, it may be useful to run _only_ a particular test suite. This can be done by specifying the test suite path: ```shell -bats tests/unit/10_apps.bats +bats tests/unit/apps_1.bats ``` It is also possible to target multiple test suites at a time. ```shell -bats tests/unit/10_apps.bats tests/unit/10_certs.bats +bats tests/unit/apps_1.bats tests/unit/certs.bats ``` -## Executing a single test +#### Executing a single test In order to increase testing velocity, a wrapper script around Bats is available that can be used to run a single test case within a suite. Tests within a suite may be listed by specifying the suite as a parameter to `bats`. ```shell -bats tests/unit/10_apps.bats +bats tests/unit/apps_1.bats ``` A single test can be specified via the `--filter` argument. The tests are selected via regex match, and all matches are executed. ```shell -bats --filter clone tests/unit/10_apps.bats +bats --filter list tests/unit/apps_1.bats ``` diff --git a/docs/enterprise/pro.md b/docs/enterprise/pro.md index bcf338c585d..cbf42932b9f 100644 --- a/docs/enterprise/pro.md +++ b/docs/enterprise/pro.md @@ -29,4 +29,4 @@ Dokku Pro has the following functionality: - Authenticated HTTP(S) endpoints for git push functionality - Single Page App (SPA) Web UI exposing app, datastore, and ssh key management -While each release is fairly feature complete, individual features and documentation will expand over time. Feature development follows a monthly release cadence, with individual bug fixes released on an as needed basis. +While each release is fairly feature complete, individual features and documentation will expand over time. Feature development follows a monthly release cadence, with individual bug fixes released on an as needed basis. diff --git a/docs/getting-started/advanced-installation.md b/docs/getting-started/advanced-installation.md index 07519b31ac1..36e83f68514 100644 --- a/docs/getting-started/advanced-installation.md +++ b/docs/getting-started/advanced-installation.md @@ -12,7 +12,6 @@ As well, you may wish to customize your installation in some other fashion. or e - [Debian Package Installation Notes](/docs/getting-started/install/debian.md) - [Docker-based Installation Notes](/docs/getting-started/install/docker.md) -- [RPM Package Installation Notes](/docs/getting-started/install/rpm.md) - [Vagrant Installation Notes](/docs/getting-started/install/vagrant.md) - [Advanced Install Customization](/docs/getting-started/advanced-installation.md) - [Automated deployment via ansible](https://github.com/dokku/ansible-dokku) @@ -23,7 +22,7 @@ You can always install Dokku straight from the latest - potentially unstable - ` ```shell # using a branch results in installing from source -wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh; +wget -NP . https://dokku.com/install/master/bootstrap.sh; sudo DOKKU_BRANCH=master bash bootstrap.sh ``` @@ -48,14 +47,14 @@ sudo SSHCOMMAND_URL=https://raw.githubusercontent.com/yourusername/sshcommand/ma The bootstrap script allows the Dokku repository URL to be overridden to bootstrap a host from your own clone of Dokku using the `DOKKU_REPO` environment variable. Example: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh; +wget -NP . https://dokku.com/install/master/bootstrap.sh; chmod +x bootstrap.sh sudo DOKKU_REPO=https://github.com/yourusername/dokku.git DOKKU_BRANCH=master ./bootstrap.sh ``` ## Custom Herokuish build -Dokku ships with a pre-built version of version of the [Herokuish](https://github.com/gliderlabs/herokuish) component by default. If you want to build your own version you can specify that with an environment variable. +Dokku ships with a pre-built version of the [Herokuish](https://github.com/gliderlabs/herokuish) component by default. If you want to build your own version you can specify that with an environment variable. ```shell git clone https://github.com/dokku/dokku.git @@ -79,7 +78,7 @@ Follow the [user management documentation](/docs/deployment/user-management.md) ## VMs with less than 1 GB of memory -Having less than 1 GB of system memory available for Dokku and its containers may result in unexpected errors, such as `! [remote rejected] master -> master (pre-receive hook declined)` during installation of NPM dependencies (https://github.com/npm/npm/issues/3867). +Having less than 1 GB of system memory available for Dokku and its containers may result in unexpected errors, such as `! [remote rejected] master -> master (pre-receive hook declined)` [during installation of NPM dependencies](https://github.com/npm/npm/issues/3867). To work around this issue, it might suffice to augment the Linux swap file size to a maximum of twice the physical memory size. @@ -90,6 +89,7 @@ cd /var touch swap.img chmod 600 swap.img +swapoff /var/swap.img dd if=/dev/zero of=/var/swap.img bs=1024k count=1000 mkswap /var/swap.img swapon /var/swap.img @@ -97,5 +97,8 @@ free echo "/var/swap.img none swap sw 0 0" >> /etc/fstab ``` +Note: it may be necessary to call swapoff, mkswap and swapon explicitly: `sudo /sbin/swapoff /var/swap.img`, etc. [Reference](https://www.digitalocean.com/community/tutorials/how-to-configure-virtual-memory-swap-file-on-a-vps) + +[Add'l Reference](https://serverfault.com/questions/994297/increase-swap-file-size-that-already-exists-ec-2-linux) diff --git a/docs/getting-started/install/debian.md b/docs/getting-started/install/debian.md index 259c1d53126..72224c38cff 100644 --- a/docs/getting-started/install/debian.md +++ b/docs/getting-started/install/debian.md @@ -3,18 +3,15 @@ As of 0.3.18, Dokku defaults to being installed via Debian package. While certain hosts may require extra work to get running, you may optionally wish to automate the installation of Dokku without the use of our `bootstrap.sh` Bash script. The following are the steps run by said script: ```shell -# install prerequisites -sudo apt-get update -qq >/dev/null -sudo apt-get -qq -y --no-install-recommends install apt-transport-https - # install docker wget -nv -O - https://get.docker.com/ | sh # install dokku -wget -nv -O - https://packagecloud.io/dokku/dokku/gpgkey | apt-key add - -OS_ID="$(lsb_release -cs 2>/dev/null || echo "bionic")" -echo "bionic focal" | grep -q "$OS_ID" || OS_ID="bionic" -echo "deb https://packagecloud.io/dokku/dokku/ubuntu/ ${OS_ID} main" | sudo tee /etc/apt/sources.list.d/dokku.list +wget -qO- https://packagecloud.io/dokku/dokku/gpgkey | sudo tee /etc/apt/trusted.gpg.d/dokku.asc +# programmatically determine distro and codename +DISTRO="$(awk -F= '$1=="ID" { print tolower($2) ;}' /etc/os-release)" +OS_ID="$(awk -F= '$1=="VERSION_CODENAME" { print tolower($2) ;}' /etc/os-release)" +echo "deb https://packagecloud.io/dokku/dokku/${DISTRO}/ ${OS_ID} main" | sudo tee /etc/apt/sources.list.d/dokku.list sudo apt-get update -qq >/dev/null sudo apt-get -qq -y install dokku sudo dokku plugin:install-dependencies --core diff --git a/docs/getting-started/install/digitalocean.md b/docs/getting-started/install/digitalocean.md index e850c4b63d0..91a67c8a973 100644 --- a/docs/getting-started/install/digitalocean.md +++ b/docs/getting-started/install/digitalocean.md @@ -6,7 +6,7 @@ 1. Login to your [DigitalOcean](https://m.do.co/c/fe06b043a083) account. 2. Click **Create a Droplet**. -3. Under **Choose an image > Marketplace**, search latest **Dokku** release for Ubuntu 20.04 _(version numbers may vary)_. +3. Under **Choose an image > Marketplace**, search latest **Dokku** release for Ubuntu 24.04 _(version numbers may vary)_. 4. Under **Choose a size**, select your machine spec. 5. Under **Choose a datacenter region**, select your region. 6. Add an SSH Key. @@ -18,5 +18,17 @@ 1. Simply add a checkmark next to the existing keys you'd like to add. 7. Under **Finalize and create**, give your Droplet a hostname _(not required)_ and click **Create**. 8. Once created, copy the IP address to your clipboard. -9. In a browser, go to the IP address you copied above and fill out the presented form to complete configuration. _Failure to do so may allow others to reconfigure SSH access on your server._ -10. Once the web UI has been submitted, you will be redirected to our [application deployment tutorial](/docs/deployment/application-deployment.md), which will guide you through deploying a sample application to your Dokku server. +9. In a terminal, ssh onto the server by running `ssh root@$SERVER_IP`, where `$SERVER_IP` is your server's IP address. +10. Remove the default nginx site via `rm /etc/nginx/sites-enabled/default` +11. If you added more than one key, use the `dokku ssh-keys:add` to add an ssh key to the dokku user: + + ```shell + echo "$CONTENTS_OF_YOUR_PUBLIC_SSH_KEY_HERE" | dokku ssh-keys:add KEY_NAME + ``` + +12. Ensure your server has a correct global hostname via `dokku domains:set-global` + + ```shell + # replace dokku.me with a hostname pointed at your server + dokku domains:set-global dokku.me + ``` diff --git a/docs/getting-started/install/docker.md b/docs/getting-started/install/docker.md index 073e6a5a291..4e99a749237 100644 --- a/docs/getting-started/install/docker.md +++ b/docs/getting-started/install/docker.md @@ -3,25 +3,79 @@ Pull the dokku/dokku image: ```shell -docker pull dokku/dokku:0.26.8 +docker pull dokku/dokku:0.38.25 ``` Next, run the image. ```shell -docker container run \ +docker container run -d \ --env DOKKU_HOSTNAME=dokku.me \ --env DOKKU_HOST_ROOT=/var/lib/dokku/home/dokku \ + --env DOKKU_LIB_HOST_ROOT=/var/lib/dokku/var/lib/dokku \ --name dokku \ --publish 3022:22 \ --publish 8080:80 \ --publish 8443:443 \ --volume /var/lib/dokku:/mnt/dokku \ --volume /var/run/docker.sock:/var/run/docker.sock \ - dokku/dokku:0.26.8 + dokku/dokku:0.38.25 ``` -The above command will start a new docker container that is ready when a message similar to `Runit started as PID 12345` appears. +Alternatively, you can use `docker-compose.yml`: + +```yaml +services: + dokku: + image: dokku/dokku:0.38.25 + container_name: dokku + network_mode: bridge + ports: + - "3022:22" + - "8080:80" + - "8443:443" + volumes: + - "/var/lib/dokku:/mnt/dokku" + - "/var/run/docker.sock:/var/run/docker.sock" + environment: + DOKKU_HOSTNAME: dokku.me + DOKKU_HOST_ROOT: /var/lib/dokku/home/dokku + DOKKU_LIB_HOST_ROOT: /var/lib/dokku/var/lib/dokku + restart: unless-stopped +``` + +## Container readiness + +The image ships with a Docker `HEALTHCHECK` that flips from `starting` to `healthy` once first-boot bootstrap is complete (skel restored, `plugin-list` plugins installed, core install triggers fired), nginx and sshd are accepting connections, and `dokku ps:restore` has finished. Until those conditions hold, the container is considered unhealthy and dependent services should not yet send traffic. + +To check the current state from the host: + +```shell +docker inspect --format='{{.State.Health.Status}}' dokku +``` + +Internally, the check is backed by a loopback-only HTTP endpoint: + +```shell +docker exec dokku curl -fsS http://127.0.0.1:18080/_dokku/health +``` + +The endpoint binds to `127.0.0.1:18080` inside the container by design - it is not published to the host and does not interfere with user app vhosts on ports 80/443. + +Compose dependents can gate on the healthcheck via `depends_on` with `condition: service_healthy`: + +```yaml +services: + dokku: + image: dokku/dokku:0.38.25 + # ... + bootstrap: + image: alpine:3.20 + command: ["echo", "dokku is ready"] + depends_on: + dokku: + condition: service_healthy +``` Dokku is run in the following configuration: @@ -40,6 +94,8 @@ Other docker container options can also be used when running Dokku, though the s ## Plugin Installation +### On Dokku start + To install custom plugins, create a `plugin-list` file in the host's `/var/lib/dokku` directory. The plugins listed herein will be automatically installed by Dokku on container boot. This file should be the following format: ```yaml @@ -53,14 +109,30 @@ postgres: https://github.com/dokku/dokku-postgres.git redis: https://github.com/dokku/dokku-redis.git ``` +### Prior to Dokku start + +The alternative is to build a custom docker image via a custom Dockerfile. This Dockerfile can run any `plugin:install` command, though the `install` trigger must be skipped via the `--skip-install-trigger`. The version installed at that time will be the one that persists. Below is an example Dockerfile showing this method. + +```Dockerfile +FROM dokku/dokku:0.38.25 +RUN dokku plugin:install https://github.com/dokku/dokku-postgres.git --skip-install-trigger +RUN dokku plugin:install https://github.com/dokku/dokku-redis.git --skip-install-trigger +``` + ## SSH Key Management -To initialize ssh-keys within the container, use `docker exec` to enter the container and run the appropriate ssh-keys commands. +To initialize ssh-keys within the container, use `docker exec` to enter the container: ```shell docker exec -it dokku bash ``` +Next, run the appropriate ssh-keys commands: + +```shell +echo "$CONTENTS_OF_YOUR_PUBLIC_SSH_KEY_HERE" | dokku ssh-keys:add KEY_NAME +``` + Please see the [user management documentation](/docs/deployment/user-management.md) for more information. ## Pushing Applications @@ -73,4 +145,4 @@ Host dokku.docker Port 3022 ``` -In the above example, the hostname `127.0.0.1` is being aliased to `dokku.docker`, while the port is being overriden to `3022`. All SSH commands - including git pushes - for the hostname `dokku.docker` will be transparently sent to `127.0.0.1:3022`. +In the above example, the hostname `127.0.0.1` is being aliased to `dokku.docker`, while the port is being overridden to `3022`. All SSH commands - including git pushes - for the hostname `dokku.docker` will be transparently sent to `127.0.0.1:3022`. diff --git a/docs/getting-started/install/dreamhost.md b/docs/getting-started/install/dreamhost.md index aec0d306d41..ccef56880db 100644 --- a/docs/getting-started/install/dreamhost.md +++ b/docs/getting-started/install/dreamhost.md @@ -19,7 +19,7 @@ source openrc.sh # Set the environment variables for DreamHost Cloud This allows OpenStack client to connect to DreamHost API endpoints. The command below creates a new server instance named `my-dokku-instance` -based on Ubuntu 18.04, with 2 GB RAM and 1 CPU (the flavor called +based on Ubuntu 24.04, with 2 GB RAM and 1 CPU (the flavor called `supersonic`), opening network port access to HTTP and SSH (the `default` security group), and the name of the chosen SSH key. This key will be automatically added to the new server in the @@ -28,7 +28,7 @@ be reused by Dokku. ```sh openstack server create \ - --image Ubuntu-18.04 \ + --image Ubuntu-24.04 \ --flavor gp1.supersonic \ --security-group default \ --key-name $YOUR_SSH_KEYNAME \ diff --git a/docs/getting-started/install/rpm.md b/docs/getting-started/install/rpm.md deleted file mode 100644 index 52c12106bea..00000000000 --- a/docs/getting-started/install/rpm.md +++ /dev/null @@ -1,27 +0,0 @@ -# RPM Package Installation Notes - -> New as of 0.8.0 *(experimental)* - -> **Warning:** Web installer is not available on CentOS. You will need to configure [SSH keys](/docs/deployment/user-management.md#adding-ssh-keys) and [virtual hosts](/docs/configuration/domains.md#customizing-hostnames) using dokku command line interface. - -Dokku defaults to being installed via RPM package on CentOS 7. While certain hosts may require extra work to get running, you may optionally wish to automate the installation of Dokku without the use of our `bootstrap.sh` Bash script. The following are the steps run by said script: - -```shell -# Install docker -curl -fsSL https://get.docker.com/ | sh - -# Install epel for nginx packages to be available -sudo yum install -y epel-release - -# Install dokku -curl -s https://packagecloud.io/install/repositories/dokku/dokku/script.rpm.sh | sudo bash -sudo yum install -y herokuish dokku -sudo dokku plugin:install-dependencies --core -systemctl start nginx - -# Enable docker and nginx on system startup -systemctl enable docker -systemctl enable nginx -``` - -The [devicemapper](https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/) is the default Docker storage driver on CentOS 7 and defaults to a configuration mode known as `loop-lvm`. This mode is designed to work out-of-the-box with no additional configuration. However, production deployments should not run under `loop-lvm` mode. The preferred configuration for production deployments is [`direct-lvm`](https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/#/configure-direct-lvm-mode-for-production). diff --git a/docs/getting-started/install/vagrant.md b/docs/getting-started/install/vagrant.md index dc79175b684..40101b84aed 100644 --- a/docs/getting-started/install/vagrant.md +++ b/docs/getting-started/install/vagrant.md @@ -21,10 +21,10 @@ # - `DOKKU_IP` # - `FORWARDED_PORT`. cd path/to/dokku - + # for most users vagrant up - + # windows users must instead use the following in an elevated command prompt vagrant up dokku-windows ``` @@ -36,13 +36,13 @@ Port 22 ``` - > For users that have customized the IP address of their VM - either in a custom `Vagrantfile` or via the `DOKKU_IP` environment variable - and are not using `10.0.0.2` for the Vagrant IP, you'll need to instead use the output of `vagrant ssh-config dokku` for your `~/.ssh/config` entry. + > For users that have customized the IP address of their VM - either in a custom `Vagrantfile` or via the `DOKKU_IP` environment variable - and are not using `10.0.0.2` for the Vagrant IP, you'll need to instead use the output of `vagrant ssh-config dokku` for your `~/.ssh/config` entry. -6. Connect to the server and add your ssh key to the install. Additionally, set the global domain name to `dokku.me`. +6. Connect to the server via `vagrant ssh` and add your ssh key to the install. Additionally, set the global domain name to `dokku.me`. ```shell # usually your key is already available under the current user's `~/.ssh/authorized_keys` file - cat ~/.ssh/authorized_keys | dokku ssh-keys:add admin + cat ~/.ssh/authorized_keys | sudo dokku ssh-keys:add admin # you can use any domain you already have access to dokku domains:set-global dokku.me diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation/index.md similarity index 68% rename from docs/getting-started/installation.md rename to docs/getting-started/installation/index.md index 4b6613d4e46..17dfd21ecd8 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation/index.md @@ -1,25 +1,18 @@ # Getting Started with Dokku -### What is Dokku? +## What is Dokku? Dokku is an extensible, open source Platform as a Service that runs on a single server of your choice. Dokku supports building apps on the fly from a `git push` via either Dockerfile or by auto-detecting the language with Buildpacks, and then starts containers based on your built image. Using technologies such as nginx and cron, Web processes are automatically routed to, while background processes and automated cron tasks are also managed by Dokku. -### System Requirements +## System Requirements To start using Dokku, you'll need a system that meets the following minimum requirements: -- A fresh installation of any of the following operating systems: - - [Ubuntu 18.04/20.04 x64](https://www.ubuntu.com/download) - - [Debian 9+ x64](https://www.debian.org/distrib/) - - [CentOS 7 x64](https://www.centos.org/download/) *(experimental)* -- A server with one of the following architectures - - AMD64 (alternatively known as `x86_64`), commonly used for Intel cloud servers - - ARMV7 (alternatively known as `armhf`), commonly used for Raspberry PI - -To avoid memory pressure during builds or runtime of your applications, we suggest the following: - -- At least 1 GB of system memory - - If your system has less than 1GB of memory, you can use [this workaround](/docs/getting-started/advanced-installation.md#vms-with-less-than-1gb-of-memory). +- Operating Systems: [Ubuntu 22.04/24.04](https://www.ubuntu.com/download) or [Debian 11+ x64](https://www.debian.org/distrib/) +- Supported Architectures: `AMD64` (`x86_64`) and `arm64` (`armv8`) +- Minimum Memory: + - Docker Scheduler: 1GB of system memory, or [add swap memory](/docs/getting-started/advanced-installation.md#vms-with-less-than-1-gb-of-memory) + - K3s Scheduler: 2GB of system memory on every node in the cluster Finally, we recommend attaching at least one domain name to your server. This is not required, but using a domain name will make app access easier. When connecting a domain, either a single domain or a wildcard may be associated to the server's IP. @@ -28,29 +21,29 @@ Finally, we recommend attaching at least one domain name to your server. This is Please see the [dns documentation](/docs/networking/dns.md) and [domains documentation](/docs/configuration/domains.md) for more details. -### Installing the latest stable version +## Installing the latest stable version -This is the simple method of installing Dokku. For alternative methods of installation, see the [advanced install guide](/docs/getting-started/advanced-installation.md#configuring). +This is the simple method of installing Dokku. For alternative methods of installation, see the [advanced install guide](/docs/getting-started/advanced-installation.md#configuring). -#### 1. Install Dokku +### 1. Install Dokku To install the latest stable version of Dokku, you can run the following shell commands: ```shell # for debian systems, installs Dokku via apt-get -wget https://raw.githubusercontent.com/dokku/dokku/v0.26.8/bootstrap.sh; -sudo DOKKU_TAG=v0.26.8 bash bootstrap.sh +wget -NP . https://dokku.com/install/v0.38.25/bootstrap.sh +sudo DOKKU_TAG=v0.38.25 bash bootstrap.sh ``` The installation process takes about 5-10 minutes, depending upon internet connection speed. -#### 2. Setup SSH key and Virtualhost Settings +### 2. Setup SSH key and Virtualhost Settings Once the installation is complete, you should configure an ssh key and set your global domain. ```shell # usually your key is already available under the current user's `~/.ssh/authorized_keys` file -cat ~/.ssh/authorized_keys | dokku ssh-keys:add admin +cat ~/.ssh/authorized_keys | sudo dokku ssh-keys:add admin # you can use any domain you already have access to # this domain should have an A record or CNAME pointing at your server's IP @@ -67,6 +60,6 @@ dokku domains:set-global 10.0.0.2.sslip.io See the [user management](/docs/deployment/user-management.md#adding-ssh-keys) and [domains documentation](/docs/configuration/domains.md#customizing-hostnames) for more information. -#### 3. Deploy your first application +### 3. Deploy your first application At this point, you should be able to [deploy to the Dokku installation](/docs/deployment/application-deployment.md). diff --git a/docs/getting-started/troubleshooting.md b/docs/getting-started/troubleshooting.md index 9402c91e864..e95c1ebbc91 100644 --- a/docs/getting-started/troubleshooting.md +++ b/docs/getting-started/troubleshooting.md @@ -1,5 +1,6 @@ # Troubleshooting +> [!IMPORTANT] > New as of 0.17.0 ``` @@ -33,7 +34,7 @@ dokku trace:off ## Common Problems -### I deployed my app but I am getting the default nginx page. +### I deployed my app but I am getting the default nginx page Most of the time it's caused by some defaults newer versions of nginx set. To make sure that's the issue you're having run the following: @@ -58,10 +59,8 @@ A value of 64 would allow domains with up to 64 characters. Set it to 128 if you Save the file and try stopping nginx and starting it again: ```shell -/etc/init.d/nginx stop -## * Stopping nginx nginx [ OK ] -/etc/init.d/nginx start -## * Starting nginx nginx [ OK ] +dokku nginx:stop +dokku nginx:start ``` --- @@ -74,12 +73,12 @@ For example, if you have an `EXPOSE` directive like so: EXPOSE 8000 ``` -The proxy port mapping will be `http:8000:8000`. +The port mapping will be `http:8000:8000`. To avoid this issue, either of the following can be done: -- Remove `EXPOSE` directive: This will require respecting the `$PORT` environment variable (automatically set by Dokku). Once that change is deployed, the port mapping should be cleared via the `dokku proxy:ports-clear $APP` command (where `$APP` is your app name). -- Update the port mapping: Updating the port mapping to redirect port `80` to your app's exposed port via `dokku proxy:ports-set $APP http:80:$EXPOSED_PORT` can also fix the issue. This will also allow certificate management and the letsencrypt plugin to work correctly. +- Remove `EXPOSE` directive: This will require respecting the `$PORT` environment variable (automatically set by Dokku). Once that change is deployed, the port mapping should be cleared via the `dokku ports:clear $APP` command (where `$APP` is your app name). +- Update the port mapping: Updating the port mapping to redirect port `80` to your app's exposed port via `dokku ports:set $APP http:80:$EXPOSED_PORT` can also fix the issue. This will also allow certificate management and the letsencrypt plugin to work correctly. See the [port management documentation](/docs/networking/port-management.md) for more information on how Dokku exposes ports for applications and how you can configure these for your app. @@ -93,7 +92,7 @@ The following error may be emitted from a deploy: The `remote rejected` error does not give enough information. Anything could have failed. Enable trace mode and begin debugging. If this does not help you, create a [gist](https://gist.github.com) containing the full log, and create an issue. -One the reasons why you may get this error is because the command that is run in the container exited (without errors). For example, (in Procfile) when you define a new worker container to run Delayed Job and use the bin/delayed_job start command. This command deamonizes the process and exists. The container thinks it's done so it closes itself. The error you get is the one above. To fix the above problem for Delayed Job, you must define the worker to user rake jobs:work, which doesn't deamonize the process. +One the reasons why you may get this error is because the command that is run in the container exited (without errors). For example, (in Procfile) when you define a new worker container to run Delayed Job and use the bin/delayed_job start command. This command deamonizes the process and exists. The container thinks it's done so it closes itself. The error you get is the one above. To fix the above problem for Delayed Job, you must define the worker to user rake jobs:work, which doesn't deamonize the process. ### I get the aforementioned error in the build phase (after turning on Dokku tracing) @@ -123,7 +122,7 @@ Sometimes (especially on DigitalOcean) deploying again seems to get past these s resolvconf -u ``` -Please see https://github.com/dokku/dokku/issues/841 and https://github.com/dokku/dokku/issues/649. +Please see [#841](https://github.com/dokku/dokku/issues/841) and [#649](https://github.com/dokku/dokku/issues/649). ### After adding an SSH key, I am told I cannot read from the remote repository on push @@ -157,7 +156,7 @@ Host DOKKU_HOSTNAME Also see [issue #116](https://github.com/dokku/dokku/issues/116). -### I successfully deployed my application with no deployment errors and receiving **Bad Gateway** when attempting to access the application. +### I successfully deployed my application with no deployment errors and receiving **Bad Gateway** when attempting to access the application In many cases the application will require the a `process.env.PORT` port opposed to a specified port. @@ -167,9 +166,13 @@ When specifying your port you may want to use something similar to: var port = process.env.PORT || 3000 ``` -Please see https://github.com/dokku/dokku/issues/282. +Please see [#282](https://github.com/dokku/dokku/issues/282). -### Deployment fails because of slow internet connection, messages shows `gzip: stdin: unexpected end of file`. +Additionally, your application should listen/bind to all interfaces (`0.0.0.0`). Many frameworks default to listening on `127.0.0.1`. + +Please see [#5798](https://github.com/dokku/dokku/issues/5798). + +### Deployment fails because of slow internet connection, messages shows `gzip: stdin: unexpected end of file` If you see output similar this when deploying: @@ -189,11 +192,11 @@ dokku config:set --global CURL_TIMEOUT=1200 dokku config:set --global CURL_CONNECT_TIMEOUT=180 ``` -Please see https://github.com/dokku/dokku/issues/509. +Please see [#509](https://github.com/dokku/dokku/issues/509). Another reason for this error (although it may respond immediately ruling out a timeout issue) may be because you've set the config setting `SSL_CERT_FILE`. Using a config setting with this key interferes with the buildpack's ability to download its dependencies, so you must rename the config setting to something else, e.g. `MY_APP_SSL_CERT_FILE`. -### Build fails with `Killed` message. +### Build fails with `Killed` message This generally occurs when the server runs out of memory. You can either add more RAM to your server or setup swap space. The follow script will create 2 GB of swap space. @@ -207,7 +210,7 @@ sudo sysctl -w vm.swappiness=10 echo vm.swappiness = 10 | sudo tee -a /etc/sysctl.conf ``` -### I successfully deployed my application with no deployment errors but I'm receiving Connection Timeout when attempting to access the application. +### I successfully deployed my application with no deployment errors but I'm receiving Connection Timeout when attempting to access the application This can occur if Dokku is running on a system with a firewall like UFW enabled (some OS versions like Ubuntu have this enabled by default). You can check if this is your case by running the following script: @@ -221,23 +224,50 @@ If the previous script returned `Status: active` and a list of ports, UFW is ena sudo ufw disable ``` -### I can't connect to my application because the server is sending an invalid response, or can't provide a secure connection. +### I can't connect to my application because the server is sending an invalid response, or can't provide a secure connection + +This is normally a config problem with your app, rather than an issue with Dokku. + +One common cause is an application configured to enforce secure connections/HSTS, but SSL is not set up. In Ruby on Rails, if your `application.rb` or `config/environments/production.rb` include the line `configure.force_ssl = true`, try commenting that line out and redeploying. If this solves the issue temporarily, longer-term you should consider [configuring SSL](/docs/configuration/ssl.md). -This isn't usually an issue with Dokku, but rather an app config problem. This can happen when your application is configured to enforce secure connections/HSTS, but you don't have SSL set up for the app. -In Rails at least, if your `application.rb` or `environmnents/production.rb` include the line `configure.force_ssl = true` which includes HSTS, try commenting that out and redeploying. +### I deployed a new application, but an existing application is being served when I visit it + +This problem has the same cause as the previous issue: your application is attempting to enforce SSL, but SSL is not configured. Disabling SSL enforcement should solve the problem. + +The underlying cause is best understood with an example. Let's say you already have `https://apples.example.com/` working, and you're trying to deploy `bananas.example.com/`. But whenever you visit `bananas.example.com` you see `apples.example.com` instead. This is caused by `bananas.example.com` forcing SSL: + +1. Your browser sends a request to `http://bananas.example.com`. +1. `bananas.example.com` issues a redirect to `https://bananas.example.com`. +1. Your browser sends a request to `https://bananas.example.com`. But you haven't configured SSL for `bananas.example.com` yet! So this request ends up routed to `https://apples.example.com`, which _does_ have an SSL config. -If this solves the issue temporarily, longer term you should consider [configuring SSL](/docs/configuration/ssl.md). ### My application deploys properly, but won't load in browser "connection refused" -This could be a result of a bad proxy configuration (`http:5000:5000` may be incorrect). Run `dokku proxy:report myapp` to check if your app has the correct proxy configuration. It should show something like the following. +This could be a result of a bad proxy configuration (`http:5000:5000` may be incorrect). Run `dokku ports:report node-js-app` to check if your app has the correct proxy configuration. It should show something like the following. ``` -=====> myapp proxy information - Proxy enabled: true - Proxy port map: http:80:5000 https:443:5000 - Proxy type: nginx +=====> node-js-app ports information + Port map: http:80:5000 https:443:5000 ``` -Set `dokku proxy:ports-set front http:80:5000` to get proxy correctly configured for http endpoint. +Set `dokku ports:set node-js-app http:80:5000` to get proxy correctly configured for http endpoint. + +### I deployed a new app but now subdomains are miss-routed + +Sometimes nginx does something funky and cant actually reload for whatever reason. + +```bash +# Validate the configuration +$ sudo nginx -t +nginx: the configuration file /etc/nginx/nginx.conf syntax is ok +nginx: configuration file /etc/nginx/nginx.conf test is successful + +$ sudo service nginx restart + +# Re-enable Letsencrypt +``` + +Example: existing AppA (https) and AppB(https), deployed NEW (non-https) then noticed right away NEW's subdomain showed AppB's content and cert but under its own subdomain (cert miss-match of course). AppB still works. AppA also changed to show AppB content and cert.. Other apps were unaffected. + +Consider caddy and traefik support via docker containers if this continues to be an issue. diff --git a/docs/getting-started/uninstalling.md b/docs/getting-started/uninstalling.md index 480c33f9a4f..c4b1a8c287d 100644 --- a/docs/getting-started/uninstalling.md +++ b/docs/getting-started/uninstalling.md @@ -9,13 +9,6 @@ While we hate to see you go, if you need to uninstall Dokku, the following may h yay -Rsn dokku ``` -## CentOS Uninstallation - -```shell -# uninstall dokku -yum remove dokku herokuish -``` - ## Debian Uninstallation ```shell diff --git a/docs/getting-started/upgrading.md b/docs/getting-started/upgrading/index.md similarity index 69% rename from docs/getting-started/upgrading.md rename to docs/getting-started/upgrading/index.md index a32332b2fd9..1def9d66129 100644 --- a/docs/getting-started/upgrading.md +++ b/docs/getting-started/upgrading/index.md @@ -1,6 +1,6 @@ # Upgrading -If your version of Dokku is pre 0.3.0 (check with `dokku version`), we recommend [a fresh install](/docs/getting-started/installation.md) on a new server. +If your version of Dokku is pre 0.3.0 (check with `dokku version`), we recommend [a fresh install](/docs/getting-started/installation/index.md) on a new server. ## Security Updates @@ -9,7 +9,6 @@ For any security related updates, please follow our [Twitter account](https://tw Your operating system may occasionally provide security updates. We recommend setting unattended upgrades for your operating system. Here are some helpful links: - [Arch Linux System Maintenance](https://wiki.archlinux.org/index.php/System_maintenance) -- [CentOS Automatic Security Updates](https://serversforhackers.com/c/automatic-security-updates-centos) - [Debian Unattended Upgrades](https://wiki.debian.org/UnattendedUpgrades) - [Ubuntu Unattended Upgrades](https://help.ubuntu.com/community/AutomaticSecurityUpdates) @@ -19,6 +18,18 @@ Docker releases updates periodically to their engine. We recommend reading their Before upgrading, check the migration guides to get comfortable with new features and prepare your deployment to be upgraded. +- [Upgrading to 0.38](/docs/appendices/0.38.0-migration-guide.md) +- [Upgrading to 0.37](/docs/appendices/0.37.0-migration-guide.md) +- [Upgrading to 0.36](/docs/appendices/0.36.0-migration-guide.md) +- [Upgrading to 0.35](/docs/appendices/0.35.0-migration-guide.md) +- [Upgrading to 0.34](/docs/appendices/0.34.0-migration-guide.md) +- [Upgrading to 0.33](/docs/appendices/0.33.0-migration-guide.md) +- [Upgrading to 0.32](/docs/appendices/0.32.0-migration-guide.md) +- [Upgrading to 0.31](/docs/appendices/0.31.0-migration-guide.md) +- [Upgrading to 0.30](/docs/appendices/0.30.0-migration-guide.md) +- [Upgrading to 0.29](/docs/appendices/0.29.0-migration-guide.md) +- [Upgrading to 0.28](/docs/appendices/0.28.0-migration-guide.md) +- [Upgrading to 0.27](/docs/appendices/0.27.0-migration-guide.md) - [Upgrading to 0.26](/docs/appendices/0.26.0-migration-guide.md) - [Upgrading to 0.25](/docs/appendices/0.25.0-migration-guide.md) - [Upgrading to 0.24](/docs/appendices/0.24.0-migration-guide.md) @@ -33,7 +44,9 @@ Before upgrading, check the migration guides to get comfortable with new feature - [Upgrading to 0.6](/docs/appendices/0.6.0-migration-guide.md) - [Upgrading to 0.5](/docs/appendices/0.5.0-migration-guide.md) -## Before upgrading +## Upgrade Process + +### Before upgrading If you'll be updating docker or the herokuish package simultaneously, it's recommended that you stop all applications before upgrading and rebuild afterwards. This is not @@ -58,20 +71,18 @@ dokku ps:stopall # for versions between 0.8.1 and 0.11.3, use dokku --quiet apps:list | xargs -L1 dokku ps:stop -# for versions versions older than 0.8.1, use +# for versions older than 0.8.1, use dokku --quiet apps | xargs -L1 dokku ps:stop ``` -## After upgrading +### Upgrading -After upgrading, you should rebuild the applications to take advantage of any -new buildpacks that were released: +> [!IMPORTANT] +> Unless specified, the `bootstrap.sh` script installs Dokku via a Debian package (`.deb`). Using either the `dokku-update` or `apt` methods are enough to perform an upgrade of Dokku. -```shell -dokku ps:rebuild --all -``` +Please read the migration guides for each version in between your currently installed version of Dokku and the version you are upgrading to in order to ensure minimal downtime. -## Upgrading using `dokku-update` +#### Upgrading using `dokku-update` We provide a helpful binary called `dokku-update`. This is a recommended package that: @@ -82,22 +93,32 @@ We provide a helpful binary called `dokku-update`. This is a recommended package This binary is available on Debian and RPM-based systems from our package repositories under the name `dokku-update`. When installing from source, this is available from a separate Github repository at [dokku/dokku-update](https://github.com/dokku/dokku-update). -## Upgrading using `apt` +#### Upgrading using `apt` If Dokku was installed in a Debian or Ubuntu system, via `apt-get install dokku` or `bootstrap.sh`, you can upgrade with `apt-get`: ```shell # update your local apt cache -sudo apt-get update -qq +sudo apt-get update # update dokku and its dependencies -sudo apt-get -qq -y --no-install-recommends install dokku herokuish sshcommand plugn gliderlabs-sigil dokku-update dokku-event-listener +sudo apt-get --no-install-recommends install dokku herokuish sshcommand plugn gliderlabs-sigil dokku-update dokku-event-listener # or just upgrade every package: sudo apt-get upgrade ``` -## Upgrading from source +To upgrade to a specific version of Dokku, specify it on the `apt-get` call: + +```shell +# specify the version _without_ the `v` prefix +sudo apt-get dokku=$VERSION +``` + +#### Upgrading from source + +> [!CAUTION] +> Dokku is not normally installed from source. Executing a source upgrade when the installation was performed via apt package may result in an inoperable system and will require manual work to get Dokku in a working state. If you installed Dokku from source (less common), upgrade with: @@ -120,3 +141,12 @@ git clone https://github.com/gliderlabs/herokuish.git cd herokuish CIRCLECI=true IMAGE_NAME=gliderlabs/herokuish BUILD_TAG=latest make build/docker ``` + +### After upgrading + +After upgrading, you should rebuild the applications to take advantage of any +new buildpacks that were released: + +```shell +dokku ps:rebuild --all +``` diff --git a/docs/getting-started/where-to-get-help.md b/docs/getting-started/where-to-get-help.md index f0060363583..5edd1c372fd 100644 --- a/docs/getting-started/where-to-get-help.md +++ b/docs/getting-started/where-to-get-help.md @@ -4,7 +4,7 @@ If you’re stuck, there are a number of places you can get help: ## The Official Dokku Website -- [https://dokku.com/docs/](/docs/) +- [https://dokku.com/docs/getting-started/installation/](/docs/getting-started/installation/) The Official Dokku website is always a great place to visit. It features links to oft-used developer tools, community plugins, and guides on using Dokku. @@ -20,9 +20,19 @@ GitHub Discussions is a new way for the Dokku community to interact with each ot ### The Slack Channel -- [Gliderlabs Slack](https://glider-slackin.herokuapp.com/) (Join the `#dokku` channel) +- [Gliderlabs Slack](https://slack.dokku.com/) (Join the `#dokku` channel) -If you’re stumped, give us a holler in the Dokku Slack channel. Someone from the development team is usually there, especially during the daylight hours for North and South American users. We’d love to hear from you, whether you need some help, want to find users in your area, or would like to donate your brand new sports car. +If you’re stumped, give us a holler in the Dokku Slack channel. Someone from the development team is usually there, especially during the daylight hours for North and South American users. We’d love to hear from you, whether you need some help, want to find users in your area, or would like to donate our brand new sports car. + +Chat is synced to Discord and IRC. + +### Discord + +- [Dokku Discord](https://discord.gg/YQjANGMZvu) + +We provide a Dokku Discord that folks can use to ask questions or comments about the project. Someone from the development team is usually there, especially during the daylight hours for North and South American users. Feel free to join us online! + +Chat is synced to IRC and Slack. ## Unmonitored Locations @@ -38,4 +48,4 @@ Tag your questions with `dokku` to enable existing users of Stack Overflow to fi - [irc.libera.chat/#dokku](https://webchat.libera.chat/?channels=dokku) -This location isn't as well monitored as Slack, and loses history. +This location isn't as well monitored as Slack, and loses history. Chat is synced to Discord and Slack diff --git a/docs/home.html b/docs/home.html index babfdc837d6..69459fccb48 100644 --- a/docs/home.html +++ b/docs/home.html @@ -1,350 +1,747 @@ - + + - Dokku - The smallest PaaS implementation you've ever seen - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - - - - - - -
-
-
-
-

- Dokku -

- -
-
-
-
- -
-
-
-

The smallest PaaS implementation you've ever seen

-

Dokku helps you build and manage the lifecycle of applications

- -
-
-
- -
-
-

Quick-start Instructions

-
-
curl | bash
-
apt
-
arch
-
-
-

-  # for debian systems, installs dokku via apt-get -

-

- - $ - wget https://raw.githubusercontent.com/dokku/dokku/v0.26.8/bootstrap.sh -

-

- - $ - sudo DOKKU_TAG=v0.26.8 bash bootstrap.sh -

-

-  # Configure your server domain via `dokku domains:set-global` -

-

-  # and user access (via `dokku ssh-keys:add`) to complete the installation -

-
+ -
-

-  # install docker -

-

- - $ - wget -nv -O - https://get.docker.com/ | sh -

-

-  # setup dokku apt repository -

-

- - $ - wget -nv -O - https://packagecloud.io/dokku/dokku/gpgkey | apt-key add - -

-

- - $ - export SOURCE="https://packagecloud.io/dokku/dokku/ubuntu/" -

-

- - $ - export OS_ID="$(lsb_release -cs 2>/dev/null || echo "bionic")" -

-

- - $ - echo "bionic focal" | grep -q "$OS_ID" || OS_ID="bionic" -

-

- - $ - echo "deb $SOURCE $OS_ID main" | tee /etc/apt/sources.list.d/dokku.list -

-

- - $ - apt-get update -

-

-  # install dokku -

-

- - $ - apt-get install dokku -

-

- - $ - dokku plugin:install-dependencies --core # run with root! -

-

-  # Configure your server domain via `dokku domains:set-global` -

-

-  # and user access (via `dokku ssh-keys:add`) to complete the installation -

-
+ + + + + + + -
-

-  # install dokku via yay -

-

- - $ - yay -S dokku -

-
-
+ +
+ +
+
+
+
Latest release v0.38.25
+

An open source PAAS alternative to Heroku.

+

Dokku helps you build and manage the lifecycle of applications from building to scaling.

+ +
+
+
+

Quick-start Instructions

-
-

Hate curl | bash? See our official Azure, DigitalOcean, and DreamHost Cloud instructions.

-

Still no love? Contributions welcome!

-
-
+
+

+ # download the installation script +

+

+ $ + wget -NP . https://dokku.com/bootstrap.sh +

+
-
- Have a question? Join us in our public slack channel! +

+ # run the installer +

+

+ $ + sudo DOKKU_TAG=v0.38.25 bash bootstrap.sh +

+
-
- - Slack Logo - -
-
+

+ # Configure your server domain +

+

+ $ + dokku domains:set-global dokku.me +

+
-
- +

+ # and your ssh key to the dokku user +

+

+ $ + PUBLIC_KEY="your-public-key-contents-here" +

+

+ $ + echo "$PUBLIC_KEY" | dokku ssh-keys:add admin +

+
-
-
-

Own Your PaaS. Infrastructure at a fraction of the cost.

-

Powered by Docker, you can install Dokku on any hardware. Use it on inexpensive cloud providers. Use the extra cash to buy a pony or feed kittens. You'll save tens of dollars a year on your dog photo sharing website.

+

+ # create your first app and you're off! +

+

+ $ + dokku apps:create test-app +

+
+
+
+

Hate curl | bash? See our official Azure, DigitalOcean, and DreamHost Cloud instructions.

+
+
+
+
+
+ +
+
+
+
+
+

No vendor lock-in

+
+
+

Own your PaaS using tools you already know

+
+
+

Powered by Docker, you can install Dokku on any hardware. Use it on inexpensive cloud providers. Use the extra cash to buy a pony or feed kittens. You'll save tens of dollars a year on your dog photo sharing website.

+

Once it's set up on a host, you can push Heroku-compatible applications to it via Git. They'll build using Heroku buildpacks and then run in isolated containers. The end result is your own, single-host version of Heroku.

+
+ +
+
+
+
+ docker +
+
+ git +
+
+
+
-
- Docker Logo -
-
- -
+ -
-
-

Easy Git Deploys. From your command-line to the cloud.

-

Once it's set up on a host, you can push Heroku-compatible applications to it via Git. They'll build using Heroku buildpacks and then run in isolated containers. The end result is your own, single-host version of Heroku.

+
+
+
+
+

Getting started is extremely easy

+
+
+
+
+ + +
+
-
- Git Logo +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

Extensible & customizable

+
+
+

Customize your PaaS using plugins

+
+
+

Write dokku plugins in any language. Share them online with others, and extend those already available. Dokku's simple core is easy to hack and add the features you need to get your job done.

+

Plugins add support for additional features in Dokku. Dokku itself is built out of plugins provides support for a few extra that are not included in the default installation.

+
+ +
+
-
- -
+ -
-
-

Extensible Platform. Customize your PaaS.

-

Write dokku plugins in any language. Share them online with others, and extend those already available. Dokku's simple core is easy to hack and add the features you need to get your job done.

+ +
+
+
+
+
+

Support Us

+
+
+

None of this would have been possible without the continued support of our supporters. Here are a few of our sponsors and backers. Join them and become a sponsor on OpenCollective!

+
+
+ You can also back us anonymously + + on Patreon + + +
+
+
+ +
+
+ +
+
+
+
-
- Extend Logo -
-
-
- -
-

Sponsor Dokku

- -
-
+ -

Here are a few of our sponsors and backers. Join them and become a sponsor on OpenCollective!

- - - -

- - - - - - - - - - -

-

- - - - - - - - - - -

+ +
+
+
+
+
+ Need something that isn't covered in the Open Source version?
+ Please consider the
+ + pro version + + +
+
+
+
+
-

You can also back us anonymously on Patreon.

+ +
+
+
+
+

Š Dokku. Website designed & developed by Yasoob Khalid

+
+
-
-
+ -
-
-

© 2013-2020 Dokku

-
-
+ - + + diff --git a/docs/networking/dns.md b/docs/networking/dns.md index 78e82ac3f9a..d3977018acb 100644 --- a/docs/networking/dns.md +++ b/docs/networking/dns.md @@ -1,6 +1,7 @@ # DNS Configuration -> Note: This is a work in progress. +> [!NOTE] +> This is a work in progress. ## DNS Versions @@ -65,7 +66,7 @@ If everything is working correctly, you should also be able to query for any oth If they all return your IP address, you have set DNS up properly for dokku. You should also be able to `ssh root@myserver.example.tld` and access your server. -Proceed with the setup instructions in the [installation documentation](/docs/getting-started/installation.md) +Proceed with the setup instructions in the [installation documentation](/docs/getting-started/installation/index.md) ### Using the root of your domain (node-js-app.example.tld) @@ -74,4 +75,4 @@ This section is a work in progress. It is incomplete. Using the 'root' of your domain is nearly identical to the previous example. * hostname is under `example.tld`, still needs `A` record. -* Update your global domain using the [domains plugin](docs/configuration/domains.md). +* Update your global domain using the [domains plugin](/docs/configuration/domains.md). diff --git a/docs/networking/network.md b/docs/networking/network.md index ae9406da2eb..aba7edf7858 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -1,17 +1,18 @@ # Network Management +> [!IMPORTANT] > New as of 0.11.0, Enhanced in 0.20.0 ``` -network:create # Creates an attachable docker network -network:destroy # Destroys a docker network -network:exists # Checks if a docker network exists -network:info # Outputs information about a docker network -network:list # Lists all docker networks -network:report [] [] # Displays a network report for one or more apps -network:rebuild # Rebuilds network settings for an app -network:rebuildall # Rebuild network settings for all apps -network:set () # Set or clear a network property for an app +network:create # Creates an attachable docker network +network:destroy # Destroys a docker network +network:exists # Checks if a docker network exists +network:info [--format text|json] # Outputs information about a docker network +network:list [--format text|json] [--dokku-managed] # Lists all docker networks +network:report [] [] # Displays a network report for one or more apps +network:rebuild # Rebuilds network settings for an app +network:rebuildall # Rebuild network settings for all apps +network:set () # Set or clear a network property for an app ``` The Network plugin allows developers to abstract the concept of container network management, allowing developers to both change what networks a given container is attached to as well as rebuild the configuration on the fly. @@ -20,6 +21,7 @@ The Network plugin allows developers to abstract the concept of container networ ### Listing networks +> [!IMPORTANT] > New as of 0.20.0, Requires Docker 1.21+ You can easily list all available networks using the `network:list` command: @@ -49,8 +51,37 @@ none test-network ``` +The `network:list` command also takes a `--format` flag, with the valid options including `text` (default) and `json`. The `json` output format can be used for automation purposes: + +```shell +dokku network:list --format json +``` + +``` +[ + {"CreatedAt":"2024-02-25T01:55:24.275184461Z","DokkuManaged":false,"Driver":"bridge","ID":"d18df2d21433","Internal":false,"IPv6":false,"Labels":{},"Name":"bridge","Scope":"local"}, + {"CreatedAt":"2024-02-25T01:55:24.275184461Z","DokkuManaged":true,"Driver":"bridge","ID":"f50fa882e7de","Internal":false,"IPv6":false,"Labels":{"com.dokku.network-name":"test-network"},"Name":"test-network","Scope":"local"}, + {"CreatedAt":"2024-02-25T01:55:24.275184461Z","DokkuManaged":false,"Driver":"host","ID":"ab6a59291443","Internal":false,"IPv6":false,"Labels":{},"Name":"host","Scope":"local"}, + {"CreatedAt":"2024-02-25T01:55:24.275184461Z","DokkuManaged":false,"Driver":"null","ID":"e2506bc8b7d7","Internal":false,"IPv6":false,"Labels":{},"Name":"none","Scope":"local"} +] +``` + +The `DokkuManaged` field is `true` for networks created by `network:create` and `false` for Docker built-in networks (such as `bridge`, `host`, and `none`) or networks created outside of Dokku (such as compose `*_default` networks). This can be used by automation to determine which networks Dokku is responsible for. + +The `network:list` command also takes a `--dokku-managed` flag, which restricts the output to only those networks created by Dokku. It can be combined with the `--format` flag: + +```shell +dokku network:list --dokku-managed +``` + +``` +=====> Networks +test-network +``` + ### Creating a network +> [!IMPORTANT] > New as of 0.20.0, Requires Docker 1.21+ Docker networks can be created via the `network:create` command. Executing this command will create an attachable `bridge` network. This can be used to route requests between containers without going through any public network. @@ -67,6 +98,7 @@ Specifying other additional flags or other types of networks can be created dire ### Destroying a network +> [!IMPORTANT] > New as of 0.20.0, Requires Docker 1.21+ A Docker network without any associated containers may be destroyed via the `network:destroy` command. Docker will refuse to destroy networks that have containers attached. @@ -75,7 +107,7 @@ A Docker network without any associated containers may be destroyed via the `net dokku network:destroy test-network ``` -```shell +``` ! WARNING: Potentially Destructive Action ! This command will destroy network test. ! To proceed, type "test" @@ -89,12 +121,13 @@ As the command is destructive, it will default to asking for confirmation before dokku --force network:destroy test-network ``` -```shell +``` -----> Destroying network test ``` ### Checking if a network exists +> [!IMPORTANT] > New as of 0.20.0, Requires Docker 1.21+ For CI/CD pipelines, it may be useful to see if an network exists before creating a new network. You can do so via the `network:exists` command: @@ -111,23 +144,37 @@ The `network:exists` command will return non-zero if the network does not exist, ### Checking network info -> New as of 0.20.0, Requires Docker 1.21+ +> [!IMPORTANT] +> New as of 0.35.3 Network information can be retrieved via the `network:info` command. This is a slightly different version of the `docker network` command. ```shell -dokku network:info test-network +dokku network:info bridge ``` ``` -// TODO +=====> bridge network information + ID: d18df2d21433 + Name: bridge + Driver: bridge + Scope: local + Dokku managed: false ``` -### Routing an app to a known ip:port combination +The `network:info` command also takes a `--format` flag, with the valid options including `text` (default) and `json`. The `json` output format can be used for automation purposes: -> New as of 0.25.0 +```shell +dokku network:info bridge --format json +``` + +``` +{"CreatedAt":"2024-02-25T01:55:24.275184461Z","DokkuManaged":false,"Driver":"bridge","ID":"d18df2d21433","Internal":false,"IPv6":false,"Labels":{},"Name":"bridge","Scope":"local"} +``` + +### Routing an app to a known ip:port combination -In some cases, it may be necessary to route an app to an existing `$IP:$PORT` combination. This is particularly the case for internal admin tools or services that aren't run by Dokku but have a web ui that would benefit from being exposed by Dokku. This can be done by setting a value for `static-web-lister` and running a few other commands when creating an app. +In some cases, it may be necessary to route an app to an existing `$IP:$PORT` combination. This is particularly the case for internal admin tools or services that aren't run by Dokku but have a web ui that would benefit from being exposed by Dokku. This can be done by using a proxy application and routing requests through that. ```shell # for a service listening on: @@ -137,43 +184,52 @@ In some cases, it may be necessary to route an app to an existing `$IP:$PORT` co # create the app dokku apps:create local-app -# set the builder to the null builder, which does nothing -dokku builder:set local-app selected null - -# set the scheduler to the null scheduler, which does nothing -dokku scheduler:set local-app selected null +# add an extra host that maps host.docker.internal to the docker gateway +dokku docker-options:add local-app deploy "--add-host=host.docker.internal:host-gateway" -# set the static-web-listener network property to the ip:port combination for your app. -dokku network:set local-app static-web-listener 127.0.0.1:8080 +# set the SERVICE_HOST to the mapped hostname +dokku config:set local-app SERVICE_HOST=host.docker.internal -# set the port map as desired for the port specified in your static-web-listener -dokku proxy:ports-set local-app http:80:8080 +# set the SERVICE_PORT to the port combination for your app +dokku config:set local-app SERVICE_PORT=8080 # set the domains desired dokku domains:set local-app local-app.dokku.me -dokku proxy:build-config local-app +# deploy the service-proxy image +dokku git:from-image local-app dokku/service-proxy:latest ``` -Only a single `$IP:$PORT` combination can be routed to for a given app, and that `$IP:$PORT` combination _must_ be accessible to the proxy, or requests to the app may not resolve. +Only a single `$IP:$PORT` combination can be routed to for a given app, and that `$IP:$PORT` combination _must_ be accessible to the service proxy on initial deploy, or the service proxy won't start. ### Attaching an app to a network +> [!IMPORTANT] > New as of 0.20.0, Requires Docker 1.21+ -Apps will default to being associated with the default `bridge` network or a network specified by the `initial-network` network property. Additionally, an app can be attached to `attachable` networks by changing the `attach-post-create` or `attach-post-deploy` network properties when using the [docker-local scheduler](/docs/advanced-usage/schedulers/docker-local.md). A change in these values will require an app deploy or rebuild. +Apps will default to being associated with the default `bridge` network or a network specified by the `initial-network` network property. Additionally, an app can be attached to `attachable` networks by changing the `attach-post-create` or `attach-post-deploy` network properties when using the [docker-local scheduler](/docs/deployment/schedulers/docker-local.md). A change in these values will require an app deploy or rebuild. ```shell # associates the network after a container is created but before it is started +# commonly used for cross-app networking dokku network:set node-js-app attach-post-create test-network # associates the network after the deploy is successful but before the proxy is updated +# used for cross-app networking when healthchecks must be invoked first dokku network:set node-js-app attach-post-deploy other-test-network # associates the network at container creation +# typically blocks access to services and external routing dokku network:set node-js-app initial-network global-network ``` +Multiple networks can also be specified for the `attach-post-create` and `attach-post-deploy` phases. + +```shell +# one or more networks can be specified +dokku network:set node-js-app attach-post-create test-network test-network-2 +``` + Setting the `attach` network property to an empty value will de-associate the container with the network. ```shell @@ -200,7 +256,10 @@ dokku network:set --global initial-network #### Network Aliases -When a container created for a deployment is being attached to a network - regardless of which `attach` property was used - a network alias of the pattern `APP.PROC_TYPE` will be added to all containers. This can be used to load-balance requests between containers. For an application named `node-js-app` with a process type of web, the network alias - or resolvable DNS record within the network - will be: +> [!NOTE] +> This feature is only available when an app has been attached to a network other than the default `bridge` network. + +When a container created for a deployment is being attached to a network - regardless of which network property was used - a network alias of the pattern `APP.PROC_TYPE` will be added to all containers. This can be used to load-balance requests between containers. For an application named `node-js-app` with a process type of web, the network alias - or resolvable DNS record within the network - will be: ``` node-js-app.web @@ -239,7 +298,7 @@ The default value may be set by passing an empty value for the option: dokku network:set node-js-app tld ``` -The `tld` property can also be set globally. The global default is emty string, and the global value is used when no app-specific value is set. +The `tld` property can also be set globally. The global default is empty string, and the global value is used when no app-specific value is set. ```shell dokku network:set --global tld svc.cluster.local @@ -262,29 +321,30 @@ Containers can be attached to a network for a variety of reasons: Whatever the reason, the semantics of the two network hooks are important and are outlined before. - `attach-post-create`: - - Phase it applies to: - - `build`: Intermediate containers created during the build process. - - `deploy`: Deployed app containers. - - `run`: Containers created by the `run` command. - - Container state on attach: `created` but not `running` - - Use case: When the container needs to access a resource on the network. - - Example: The app needs to talk to a database on the same network when it first boots. + - Phase it applies to: + - `build`: Intermediate containers created during the build process. + - `deploy`: Deployed app containers. + - `run`: Containers created by the `run` command. + - Container state on attach: `created` but not `running` + - Use case: When the container needs to access a resource on the network. + - Example: The app needs to talk to a database on the same network when it first boots. - `attach-post-deploy` - - Phase it applies to: - - `deploy`: Deployed app containers. - - Container state on attach: `running` - - Use case: When another container on the network needs to access _this_ container. - - Example: A background process needs to communicate with the web process exposed by this container. + - Phase it applies to: + - `deploy`: Deployed app containers. + - Container state on attach: `running` + - Use case: When another container on the network needs to access _this_ container. + - Example: A background process needs to communicate with the web process exposed by this container. - `initial-network`: - - Phase it applies to: - - `build`: Intermediate containers created during the build process. - - `deploy`: Deployed app containers. - - `run`: Containers created by the `run` command. - - Container state on attach: `created` - - Use case: When another container on the network is already running and needed by this container. - - Example: A key-value store exposing itself to all your apps may be on the `initial-network`. + - Phase it applies to: + - `build`: Intermediate containers created during the build process. + - `deploy`: Deployed app containers. + - `run`: Containers created by the `run` command. + - Container state on attach: `created` + - Use case: When another container on the network is already running and needed by this container. + - Example: A key-value store exposing itself to all your apps may be on the `initial-network`. -> Warning: If the attachment fails during the `running` container state, this may result in your application failing to respond to proxied requests once older containers are removed. +> [!WARNING] +> If the attachment fails during the `running` container state, this may result in your application failing to respond to proxied requests once older containers are removed. ### Rebuilding network settings @@ -308,7 +368,7 @@ dokku network:rebuildall ### Container network interface binding -> This functionality does not control the `--network` docker flag. Please use the [docker-options plugin](docs/advanced-usage/docker-options.md) to manage this flag. +> This functionality does not control the `--network` docker flag. Please use the [docker-options plugin](/docs/advanced-usage/docker-options.md) to manage this flag. By default, an app will only bind to the internal interface. This behavior can be modified per app by changing the `bind-all-interfaces` network property. @@ -336,7 +396,7 @@ CONTAINER ID IMAGE COMMAND CREATED As such, the container's IP address will be an internal IP, and thus it is only accessible on the host itself: -``` +```shell docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node-js-app.web.1 ``` @@ -382,7 +442,7 @@ dokku network:report =====> node-js-app network information Network attach post create: Network attach post deploy: - Network bind all interfaces: false + Network bind all interfaces: Network computed attach post create: Network computed attach post deploy: Network computed bind all interfaces:false @@ -390,7 +450,7 @@ dokku network:report Network computed tld: Network global attach post create: Network global attach post deploy: - Network global bind all interfaces:false + Network global bind all interfaces: Network global initial network: Network global tld: Network initial network: @@ -399,7 +459,7 @@ dokku network:report =====> python-sample network information Network attach post create: Network attach post deploy: - Network bind all interfaces: false + Network bind all interfaces: Network computed attach post create: Network computed attach post deploy: Network computed bind all interfaces:false @@ -407,7 +467,7 @@ dokku network:report Network computed tld: Network global attach post create: Network global attach post deploy: - Network global bind all interfaces:false + Network global bind all interfaces: Network global initial network: Network global tld: Network initial network: @@ -416,7 +476,7 @@ dokku network:report =====> ruby-sample network information Network attach post create: Network attach post deploy: - Network bind all interfaces: false + Network bind all interfaces: Network computed attach post create: Network computed attach post deploy: Network computed bind all interfaces:false @@ -424,7 +484,7 @@ dokku network:report Network computed tld: Network global attach post create: Network global attach post deploy: - Network global bind all interfaces:false + Network global bind all interfaces: Network global initial network: Network global tld: Network initial network: @@ -432,6 +492,8 @@ dokku network:report Network web listeners: ``` +The per-app and `global-` keys for each property hold the raw values and are empty when nothing has been set. The `computed-` keys hold the effective value, falling back to the global value (where one has been set) and then to the built-in default (`false` for `bind-all-interfaces`, empty otherwise). + You can run the command for a specific app also. ```shell @@ -442,7 +504,7 @@ dokku network:report node-js-app =====> node-js-app network information Network attach post create: Network attach post deploy: - Network bind all interfaces: false + Network bind all interfaces: Network computed attach post create: Network computed attach post deploy: Network computed bind all interfaces:false @@ -450,7 +512,7 @@ dokku network:report node-js-app Network computed tld: Network global attach post create: Network global attach post deploy: - Network global bind all interfaces:false + Network global bind all interfaces: Network global initial network: Network global tld: Network initial network: @@ -463,3 +525,27 @@ You can pass flags which will output only the value of the specific information ```shell dokku network:report node-js-app --network-bind-all-interfaces ``` + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `network:report`. The JSON keys emitted by `network:report --format json` are the same names with the leading `--network-` stripped (e.g. `attach-post-create`, `global-attach-post-create`, `computed-attach-post-create`). Legacy keys with the `network-` prefix (e.g. `network-attach-post-create`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `attach-post-create` | app + global | none | `--network-attach-post-create`, `--network-global-attach-post-create`, `--network-computed-attach-post-create` | Networks attached to a container immediately after creation, before the deploy phase | +| `attach-post-deploy` | app + global | none | `--network-attach-post-deploy`, `--network-global-attach-post-deploy`, `--network-computed-attach-post-deploy` | Networks attached to a container after it passes healthchecks | +| `bind-all-interfaces` | app + global | `false` | `--network-bind-all-interfaces`, `--network-global-bind-all-interfaces`, `--network-computed-bind-all-interfaces` | When `true`, binds containers to `0.0.0.0` instead of the default Docker network address | +| `initial-network` | app + global | none | `--network-initial-network`, `--network-global-initial-network`, `--network-computed-initial-network` | Network attached at container creation time | +| `static-web-listener` | app only | none | `--network-static-web-listener` | Static `host:port` override used in proxy templates when no container is running | +| `tld` | app + global | none | `--network-tld`, `--network-global-tld`, `--network-computed-tld` | Top-level domain appended to auto-generated app vhosts | + +### Read-only flags + +The following flag surfaces in `network:report` but is not managed by `network:set` - it is derived from the running app state: + +| Flag | Description | +|---|---| +| `--network-web-listeners` | Current `web` process listeners (host:port) for the deployed app | diff --git a/docs/networking/port-management.md b/docs/networking/port-management.md index 8d46484839f..bdd179677e8 100644 --- a/docs/networking/port-management.md +++ b/docs/networking/port-management.md @@ -1,35 +1,54 @@ # Port Management -> New as of 0.5.0, Enhanced in 0.6.0 +> [!IMPORTANT] +> New as of 0.31.0, replaces the previous `proxy:ports*` commands ``` -proxy:ports # List proxy port mappings for an app -proxy:ports-add :: [::...] # Add proxy port mappings to an app -proxy:ports-clear # Clear all proxy port mappings for an app -proxy:ports-remove [|::...] # Remove specific proxy port mappings from an app -proxy:ports-set :: [::...] # Set proxy port mappings for an app +ports:list # List port mappings for an app +ports:add :: [::...] # Add port mappings to an app +ports:clear # Clear all port mappings for an app +ports:remove [|::...] # Remove specific port mappings from an app +ports:set :: [::...] # Set port mappings for an app ``` -In Dokku 0.5.0, port proxying was decoupled from the `nginx-vhosts` plugin into the proxy plugin. Dokku 0.6.0 introduced the ability to map host ports to specific container ports. In the future this will allow other proxy software - such as HAProxy or Caddy - to be used in place of nginx. - ## Usage -> Warning: Mapping alternative ports may conflict with the active firewall installed on your server or hosting provider. Such software includes - but is not limited to - AWS Security Groups, iptables, and UFW. Please consult the documentation for those softwares as applicable. - -> New as of 0.6.0 +> [!WARNING] +> Mapping alternative ports may conflict with the active firewall installed on your server or hosting provider. Such software includes - but is not limited to - AWS Security Groups, iptables, and UFW. Please consult the documentation for those softwares as applicable. +> +> Users should also avoid setting the `PORT` environment variable. Dokku will use port mappings to set this value. Overriding this manually may cause issues in application routing. -You can now configure `host -> container` port mappings with the `proxy:ports-*` commands. This mapping is currently supported by the built-in nginx-vhosts plugin. +You can now configure `host -> container` port mappings with the `ports:*` commands. This mapping is currently supported by the built-in nginx-vhosts plugin. By default, buildpack apps and dockerfile apps **without** explicitly exposed ports (i.e. using the `EXPOSE` directive) will be configured with a listener on port `80` (and additionally a listener on 443 if ssl is enabled) that will proxy to the application container on port `5000`. Dockerfile apps **with** explicitly exposed ports will be configured with a listener on each exposed port and will proxy to that same port of the deployed application container. -> Note: This default behavior **will not** be automatically changed on subsequent pushes and must be manipulated with the `proxy:ports-*` commands detailed below. +> [!NOTE] +> This default behavior **will not** be automatically changed on subsequent pushes and must be manipulated with the `ports:*` commands detailed below. + +### Port Scheme + +The proxy port scheme is as follows: + +- `SCHEME:HOST_PORT:CONTAINER_PORT` + +The scheme metadata can be used by proxy implementations in order to properly handle proxying of requests. For example, the built-in `nginx-vhosts` proxy implementation supports the `http`, `https`, `grpc` and `grpcs` schemes. For the `grpc` and `grpcs` see [nginx blog post on grpc](https://www.nginx.com/blog/nginx-1-13-10-grpc/). + +Developers of proxy implementations are encouraged to use whatever schemes make the most sense, and ignore configurations which they do not support. For instance, a `udp` proxy implementation can safely ignore `http` and `https` port mappings. + +To change the proxy implementation in use for an application, use the `proxy:set` command: + +```shell +# no validation will be performed against +# the specified proxy implementation +dokku proxy:set node-js-app type nginx +``` ### Listing port mappings -To inspect the port mapping for a given application, use the `proxy:ports` command: +To inspect the port mapping for a given application, use the `ports:list` command: ```shell -dokku proxy:ports node-js-app +dokku ports:list node-js-app ``` ``` @@ -60,15 +79,13 @@ curl http://node-js-app.dokku.me:8080 curl: (7) Failed to connect to node-js-app.dokku.me port 8080: Connection refused ``` -However, we can use the `proxy:ports-add` command to add a second external port mapping - `8080` - to our application's port `5000`. +However, we can use the `ports:add` command to add a second external port mapping - `8080` - to our application's port `5000`. ```shell -dokku proxy:ports-add node-js-app http:8080:5000 +dokku ports:add node-js-app http:8080:5000 ``` ``` ------> Setting config vars - DOKKU_PROXY_PORT_MAP: http:80:5000 http:8080:5000 -----> Configuring node-js-app.dokku.me...(using built-in template) -----> Creating http nginx.conf Reloading nginx @@ -96,15 +113,13 @@ Hello World! ### Setting all port mappings at once -Port mappings can also be force set using the `proxy:ports-set` command. +Port mappings can also be force set using the `ports:set` command. ```shell -dokku proxy:ports-set node-js-app http:8080:5000 +dokku ports:set node-js-app http:8080:5000 ``` ``` ------> Setting config vars - DOKKU_PROXY_PORT_MAP: http:80:5000 http:8080:5000 -----> Configuring node-js-app.dokku.me...(using built-in template) -----> Creating http nginx.conf Reloading nginx @@ -112,21 +127,22 @@ dokku proxy:ports-set node-js-app http:8080:5000 ### Removing a port mapping -A port mapping can be removed using the `proxy:ports-remove` command if it no longer necessary: +A port mapping can be removed using the `ports:remove` command if it no longer necessary: ```shell -dokku proxy:ports-remove node-js-app http:80:5000 +dokku ports:remove node-js-app http:80:5000 ``` Ports may also be removed by specifying only the `host-port` value. This effectively acts as a wildcard and removes all mappings for that particular host port. ```shell -dokku proxy:ports-remove node-js-app http:80 +dokku ports:remove node-js-app http:80 ``` ## Port management by Deployment Method -> Warning: If you set a proxy port map but _do not have a global domain set_, Dokku will reset that map upon first deployment. +> [!WARNING] +> If you set a port map but _do not have a global domain set_, Dokku will reset that map upon first deployment. ### Buildpacks @@ -136,7 +152,7 @@ For buildpack deployments, your application _must_ respect the `PORT` environmen > Changed as of 0.5.0 -Dokku's default proxy implementation - nginx - supports HTTP and GRPC request proxying. At this time, we do not support proxying plain TCP or UDP ports. UDP ports can be exposed by disabling the nginx proxy with `dokku proxy:disable myapp`. If you would like to investigate alternative proxy methods, please refer to our [proxy management documentation](/docs/networking/proxy-management.md). +Dokku's default proxy implementation - nginx - supports HTTP and GRPC request proxying. At this time, we do not support proxying plain TCP or UDP ports. UDP ports can be exposed by disabling the nginx proxy with `dokku proxy:disable myapp` and manually exposing the ports via the `docker-options` plugin. For example, `dokku docker-options:add myapp deploy "-p 2456:2456/udp"`. If you would like to investigate alternative proxy methods, please refer to our [proxy management documentation](/docs/networking/proxy-management.md). #### Applications using EXPOSE @@ -145,19 +161,19 @@ Dokku will extract all tcp ports exposed using the `EXPOSE` directive (one port For example, if the Dokku installation is configured with the domain `dokku.me` and an application named `node-js-app` is deployed with following Dockerfile: ``` -FROM ubuntu:18.04 +FROM ubuntu:24.04 EXPOSE 1234 -RUN python -m SimpleHTTPServer 1234 +CMD python3 -m SimpleHTTPServer 1234 ``` The application would be exposed to the user at `node-js-app.dokku.me:1234`. If this is not desired, the following application configuration may be applied: ```shell # add a port mapping to port 80 -dokku proxy:ports-add node-js-app http:80:1234 +dokku ports:add node-js-app http:80:1234 # remove the incorrect port mapping -dokku proxy:ports-remove node-js-app http:1234:1234 +dokku ports:remove node-js-app http:1234:1234 ``` #### Applications not using EXPOSE @@ -167,24 +183,104 @@ Any application that does not use an `EXPOSE` directive will result in Dokku def - modify your application to support the `PORT` environment variable. - switch to using an `EXPOSE` directive in your Dockerfile. -#### Switching between `EXPOSE` usage modes +### Displaying ports reports for an app + +You can get a report about the app's ports status using the `ports:report` command: + +```shell +dokku ports:report +``` + +``` +=====> node-js-app ports information + Port map detected: http:80:5000 + Port map: http:80:5000 https:443:5000 +=====> python-sample ports information + Port map detected: http:80:5000 + Port map: http:80:5000 +=====> ruby-sample ports information + Port map detected: http:80:5000 + Port map: http:80:5000 +``` + +You can run the command for a specific app also. + +```shell +dokku ports:report node-js-app +``` + +``` +=====> node-js-app ports information + Port map detected: http:80:5000 + Port map: http:80:5000 https:443:5000 +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku ports:report node-js-app --ports-map +``` -When switching between `EXPOSE` usage modes, it is important to reset your port management. The following two commands can be used to reset your state and redeploy your application. +Use `--ports-map-json` or `--ports-map-detected-json` to get the same data as a JSON array of objects. Each object has the following fields: + +- `scheme`: the port scheme (e.g. `http`, `https`, `udp`) +- `host_port`: the host/listener port +- `container_port`: the container port ```shell -# assuming your application is called `node-js-app` -dokku config:unset --no-restart node-js-app DOKKU_DOCKERFILE_PORTS PORT -dokku proxy:ports-clear node-js-app +dokku ports:report node-js-app --ports-map-json ``` -### Docker Image +```json +[ + { + "scheme": "http", + "host_port": 80, + "container_port": 5000 + }, + { + "scheme": "https", + "host_port": 443, + "container_port": 5000 + } +] +``` -When deploying an image, we will use `docker inspect` to extract the `ExposedPorts` configuration and if defined, use that to populate port mapping. If this behavior is not desired, you can override that configuration variable with the following commands. +The JSON output can be processed with tools such as `jq`: ```shell -# assuming your application is called `node-js-app` -dokku config:set node-js-app DOKKU_DOCKERFILE_PORTS="1234/tcp 80/tcp" -dokku proxy:ports-clear node-js-app +dokku ports:report node-js-app --ports-map-json | jq '.[].host_port' +``` + +``` +80 +443 ``` -All other port-related behavior is the same as when deploying via Dockerfile. +```shell +dokku ports:report node-js-app --ports-map-detected-json | jq '.[0].container_port' +``` + +``` +5000 +``` + +## Properties + +### Configured flags + +The following flags surface configured port mappings managed by `ports:set` / `ports:add` / `ports:remove` / `ports:clear`: + +| Flag | Description | +|---|---| +| `--ports-map` | Configured port mappings as space-separated `scheme:host-port:container-port` values | +| `--ports-map-json` | Configured port mappings as a JSON array of `{scheme, host_port, container_port}` objects | + +### Read-only flags + +The following flags surface in `ports:report` but are not managed by `ports:set` - they are derived from the deploy: + +| Flag | Description | +|---|---| +| `--ports-map-detected` | Port mapping inferred from `EXPOSE` directives or the running container | +| `--ports-map-detected-json` | Detected port mappings as a JSON array of `{scheme, host_port, container_port}` objects | diff --git a/docs/networking/proxies/caddy.md b/docs/networking/proxies/caddy.md new file mode 100644 index 00000000000..e126dd7d844 --- /dev/null +++ b/docs/networking/proxies/caddy.md @@ -0,0 +1,288 @@ +# Caddy Proxy + +> [!IMPORTANT] +> New as of 0.28.0 + +Dokku provides integration with the [Caddy](https://caddyserver.com/) proxy service by utilizing the Docker label-based integration implemented by [Caddy Docker Proxy](https://github.com/lucaslorentz/caddy-docker-proxy). + +``` +caddy:report [] [] # Displays a caddy report for one or more apps +caddy:logs [--num num] [--tail] # Display caddy log output +caddy:set [|--global] () # Set or clear an caddy property for an app or globally +caddy:show-config # Display caddy compose config +caddy:start # Starts the caddy server +caddy:stop # Stops the caddy server +``` + +## Requirements + +Using the `caddy` plugin integration requires the `docker-compose-plugin` for Docker. See [this document](https://docs.docker.com/compose/install/) from the Docker documentation for more information on the installation process for the `docker-compose-plugin`. + +## Usage + +> [!WARNING] +> As using multiple proxy plugins on a single Dokku installation can lead to issues routing requests to apps, doing so should be avoided. As the default proxy implementation is nginx, users are encouraged to stop the nginx service before switching to Caddy. + +The Caddy plugin has specific rules for routing requests: + +- Caddy integration is exposed via docker labels attached to containers. Changes in labels require either app deploys or rebuilds. +- While Caddy will respect labels associated with other containers, only `web` containers have Caddy labels injected by the plugin. +- Only `http:80` and `https:443` port mappings are supported. +- Caddy will automatically enable SSL if the letsencrypt email property is set. SSL will be disabled otherwise. +- If no `http:80` mapping is found, the first `http` port mapping is used for http requests. +- If no `https:443` mapping is found, the first `https` port mapping is used for https requests. +- If no `https` mapping is found, the container port from `http:80` will be used for https requests. +- Requests are routed as soon as the container is running and passing healthchecks. + +### Switching to Caddy + +To use the Caddy plugin, use the `proxy:set` command for the app in question: + +```shell +dokku proxy:set node-js-app type caddy +``` + +This will enable the docker label-based Caddy integration. All future deploys will inject the correct labels for Caddy to read and route requests to containers. Due to the docker label-based integration used by Caddy, a single deploy or rebuild will be required before requests will route successfully. + +```shell +dokku ps:rebuild node-js-app +``` + +Any changes to domains or port mappings will also require either a deploy or rebuild. + +### Starting Caddy container + +Caddy can be started via the `caddy:start` command. This will start a Caddy container via the `docker compose up` command. + +```shell +dokku caddy:start +``` + +### Stopping the Caddy container + +Caddy may be stopped via the `caddy:stop` command. + +```shell +dokku caddy:stop +``` + +The Caddy container will be stopped and removed from the system. If the container is not running, this command will do nothing. + +### Showing the Caddy compose config + +For debugging purposes, it may be useful to show the Caddy compose config. This can be achieved via the `caddy:show-config` command. + +```shell +dokku caddy:show-config +``` + +### Customizing the Caddy container image + +While the default Caddy image is hardcoded, users may specify an alternative by setting the `image` property with the `--global` flag: + +```shell +dokku caddy:set --global image lucaslorentz/caddy-docker-proxy:2.7 +``` + +#### Checking the Caddy container's logs + +It may be necessary to check the Caddy container's logs to ensure that Caddy is operating as expected. This can be performed with the `caddy:logs` command. + +```shell +dokku caddy:logs +``` + +This command also supports the following modifiers: + +```shell +--num NUM # the number of lines to display +--tail # continually stream logs +``` + +You can use these modifiers as follows: + +```shell +dokku caddy:logs --tail --num 10 +``` + +The above command will show logs continually from the caddy container, with an initial history of 10 log lines + +### Changing the Caddy log level + +Caddy log output is set to `ERROR` by default. It may be changed by setting the `log-level` property with the `--global` flag: + +```shell +dokku caddy:set --global log-level DEBUG +``` + +After modifying, the Caddy container will need to be restarted. + +### Label Management + +The Caddy plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure Caddy behavior beyond what the plugin provides by default. + +Refer to the upstream [caddy-docker-proxy](https://github.com/lucaslorentz/caddy-docker-proxy) documentation for more information on what labels are available. + +#### Adding a label + +To add a custom container label to an app, use the `caddy:labels:add` command: + +```shell +dokku caddy:labels:add node-js-app caddy.directive value +``` + +This will add the label `caddy.directive=value` to the app's containers. After adding a label, you will need to rebuild or redeploy the app for the label to be applied to running containers. + +```shell +dokku ps:rebuild node-js-app +``` + +#### Removing a label + +To remove a custom container label from an app, use the `caddy:labels:remove` command: + +```shell +dokku caddy:labels:remove node-js-app caddy.directive +``` + +This will remove the specified label from the app. After removing a label, you will need to rebuild or redeploy the app for the change to be applied to running containers. + +```shell +dokku ps:rebuild node-js-app +``` + +#### Showing labels + +To view all custom container labels for an app, use the `caddy:labels:show` command: + +```shell +dokku caddy:labels:show node-js-app +``` + +To view a specific label value, provide the label name: + +```shell +dokku caddy:labels:show node-js-app caddy.directive +``` + +### SSL Configuration + +The caddy plugin only supports automatic ssl certificates from its letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. + +#### Enabling letsencrypt integration + +By default, letsencrypt is disabled and https port mappings are ignored. To enable, set the `letsencrypt-email` property with the `--global` flag: + +```shell +dokku caddy:set --global letsencrypt-email automated@dokku.sh +``` + +After enabling, the Caddy container will need to be restarted and apps will need to be rebuilt. All http requests will then be redirected to https. + +#### Customizing the letsencrypt server + +The letsencrypt integration is set to the production letsencrypt server by default. To change this, set the `letsencrypt-server` property with the `--global` flag: + +```shell +dokku caddy:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory +``` + +After enabling, the Caddy container will need to be restarted and apps will need to be rebuilt to retrieve certificates from the new server. + +### Using Caddy's Internal TLS server + +To switch to Caddy's internal TLS server for certificate provisioning, set the `tls-internal` property. + +```shell +dokku caddy:set node-js-app tls-internal true +``` + +The default value may also be configured globally with the `--global` flag. Per-app values take precedence over the global value when set. + +```shell +dokku caddy:set --global tls-internal true +``` + +Both the per-app and the global value may be unset by setting a blank value. + +```shell +dokku caddy:set node-js-app tls-internal +dokku caddy:set --global tls-internal +``` + +## Displaying Caddy reports for an app + +You can get a report about the app's Caddy config using the `caddy:report` command: + +```shell +dokku caddy:report +``` + +``` +=====> node-js-app caddy information + Caddy computed image: lucaslorentz/caddy-docker-proxy:2.7 + Caddy computed letsencrypt email: + Caddy computed letsencrypt server: https://acme-v02.api.letsencrypt.org/directory + Caddy computed log level: ERROR + Caddy computed polling interval: 5s + Caddy computed tls internal: false + Caddy global image: + Caddy global letsencrypt email: + Caddy global letsencrypt server: + Caddy global log level: + Caddy global polling interval: + Caddy global tls internal: + Caddy tls internal: +``` + +The `global-` keys hold the raw global value and are empty when nothing has been set globally. The `computed-` keys hold the effective value used at deploy time, falling back to the global value (where one has been set) and then to the built-in default. The bare `tls-internal` key holds the raw per-app value. + +You can run the command for a specific app also. + +```shell +dokku caddy:report node-js-app +``` + +``` +=====> node-js-app caddy information + Caddy computed image: lucaslorentz/caddy-docker-proxy:2.7 + Caddy computed letsencrypt email: + Caddy computed letsencrypt server: https://acme-v02.api.letsencrypt.org/directory + Caddy computed log level: ERROR + Caddy computed polling interval: 5s + Caddy computed tls internal: false + Caddy global image: + Caddy global letsencrypt email: + Caddy global letsencrypt server: + Caddy global log level: + Caddy global polling interval: + Caddy global tls internal: + Caddy tls internal: +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku caddy:report node-js-app --caddy-computed-image +``` + +## Properties + +### Settable properties + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `image` | global only | _parsed from `plugins/caddy-vhosts/Dockerfile`_ | `--caddy-global-image`, `--caddy-computed-image` | Docker image used to run the Caddy container | +| `letsencrypt-email` | global only | none | `--caddy-global-letsencrypt-email`, `--caddy-computed-letsencrypt-email` | Contact email enabling letsencrypt; empty disables https issuance | +| `letsencrypt-server` | global only | `https://acme-v02.api.letsencrypt.org/directory` | `--caddy-global-letsencrypt-server`, `--caddy-computed-letsencrypt-server` | ACME directory used when requesting certificates | +| `log-level` | global only | `ERROR` | `--caddy-global-log-level`, `--caddy-computed-log-level` | Caddy log level | +| `polling-interval` | global only | `5s` | `--caddy-global-polling-interval`, `--caddy-computed-polling-interval` | Frequency at which Caddy polls the Docker API for label changes | +| `tls-internal` | app + global | `false` | `--caddy-tls-internal`, `--caddy-global-tls-internal`, `--caddy-computed-tls-internal` | When `true`, uses Caddy's built-in self-signed TLS instead of letsencrypt | + +### Internal properties + +The following properties are not managed by `caddy:set` but are recorded internally by the plugin: + +| Property | Description | Source | +|---|---|---| +| `proxy-status` | `started`/`stopped` state of the caddy compose project | `cmd-caddy-start`/`cmd-caddy-stop` in `plugins/caddy-vhosts/command-functions` | diff --git a/docs/networking/proxies/haproxy.md b/docs/networking/proxies/haproxy.md new file mode 100644 index 00000000000..4f393ca1852 --- /dev/null +++ b/docs/networking/proxies/haproxy.md @@ -0,0 +1,268 @@ +# Haproxy Proxy + +> [!IMPORTANT] +> New as of 0.28.0 + +Dokku provides integration with the [Haproxy](http://www.haproxy.org) proxy service by utilizing the Docker label-based integration implemented by [EasyHaproxy](https://github.com/byjg/docker-easy-haproxy). + +``` +haproxy:report [] [] # Displays a haproxy report for one or more apps +haproxy:logs [--num num] [--tail] # Display haproxy log output +haproxy:set () # Set or clear an haproxy property for an app +haproxy:show-config # Display haproxy compose config +haproxy:start # Starts the haproxy server +haproxy:stop # Stops the haproxy server +``` + +## Requirements + +Using the `haproxy` plugin integration requires the `docker-compose-plugin` for Docker. See [this document](https://docs.docker.com/compose/install/) from the Docker documentation for more information on the installation process for the `docker-compose-plugin`. + +## Usage + +> [!WARNING] +> As using multiple proxy plugins on a single Dokku installation can lead to issues routing requests to apps, doing so should be avoided. As the default proxy implementation is nginx, users are encouraged to stop the nginx service before switching to Haproxy. + +The Haproxy plugin has specific rules for routing requests: + +- Haproxy integration is exposed via docker labels attached to containers. Changes in labels require either app deploys or rebuilds. +- While Haproxy will respect labels associated with other containers, only `web` containers have Haproxy labels injected by the plugin. +- Only `http:80` and `https:443` port mappings are supported at this time. +- Requests are routed as soon as the container is running and passing healthchecks. + +### Switching to Haproxy + +To use the Haproxy plugin, use the `proxy:set` command for the app in question: + +```shell +dokku proxy:set node-js-app type haproxy +``` + +This will enable the docker label-based Haproxy integration. All future deploys will inject the correct labels for Haproxy to read and route requests to containers. Due to the docker label-based integration used by Haproxy, a single deploy or rebuild will be required before requests will route successfully. + +```shell +dokku ps:rebuild node-js-app +``` + +Any changes to domains or port mappings will also require either a deploy or rebuild. + +### Starting Haproxy container + +Haproxy can be started via the `haproxy:start` command. This will start a Haproxy container via the `docker compose up` command. + +```shell +dokku haproxy:start +``` + +### Stopping the Haproxy container + +Haproxy may be stopped via the `haproxy:stop` command. + +```shell +dokku haproxy:stop +``` + +The Haproxy container will be stopped and removed from the system. If the container is not running, this command will do nothing. + +### Showing the Haproxy compose config + +For debugging purposes, it may be useful to show the Haproxy compose config. This can be achieved via the `haproxy:show-config` command. + +```shell +dokku haproxy:show-config +``` + +### Customizing the Haproxy container image + +While the default Haproxy image is hardcoded, users may specify an alternative by setting the `image` property with the `--global` flag: + +```shell +dokku haproxy:set --global image byjg/easy-haproxy:4.0.0 +``` + +#### Checking the Haproxy container's logs + +It may be necessary to check the Haproxy container's logs to ensure that Haproxy is operating as expected. This can be performed with the `haproxy:logs` command. + +```shell +dokku haproxy:logs +``` + +This command also supports the following modifiers: + +```shell +--num NUM # the number of lines to display +--tail # continually stream logs +``` + +You can use these modifiers as follows: + +```shell +dokku haproxy:logs --tail --num 10 +``` + +The above command will show logs continually from the vector container, with an initial history of 10 log lines + +### Changing the Haproxy log level + +Haproxy log output is set to `ERROR` by default. It may be changed by setting the `log-level` property with the `--global` flag: + +```shell +dokku haproxy:set --global log-level DEBUG +``` + +After modifying, the Haproxy container will need to be restarted. + +### Changing the Haproxy refresh interval + +Haproxy polls the Docker API for label changes every `10` seconds by default. The interval may be changed by setting the `refresh-conf` property with the `--global` flag: + +```shell +dokku haproxy:set --global refresh-conf 5 +``` + +The `refresh-conf` property is global-only and cannot be set on a per-app basis. Setting an empty value will reset it to the default. After modifying, the Haproxy container will need to be restarted. + +### Label Management + +The Haproxy plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure Haproxy behavior beyond what the plugin provides by default. + +Refer to the upstream [EasyHaproxy](https://github.com/byjg/docker-easy-haproxy) documentation for more information on what labels are available. + +#### Adding a label + +To add a custom container label to an app, use the `haproxy:labels:add` command: + +```shell +dokku haproxy:labels:add node-js-app haproxy.directive value +``` + +This will add the label `haproxy.directive=value` to the app's containers. After adding a label, you will need to rebuild or redeploy the app for the label to be applied to running containers. + +```shell +dokku ps:rebuild node-js-app +``` + +#### Removing a label + +To remove a custom container label from an app, use the `haproxy:labels:remove` command: + +```shell +dokku haproxy:labels:remove node-js-app haproxy.directive +``` + +This will remove the specified label from the app. After removing a label, you will need to rebuild or redeploy the app for the change to be applied to running containers. + +```shell +dokku ps:rebuild node-js-app +``` + +#### Showing labels + +To view all custom container labels for an app, use the `haproxy:labels:show` command: + +```shell +dokku haproxy:labels:show node-js-app +``` + +To view a specific label value, provide the label name: + +```shell +dokku haproxy:labels:show node-js-app haproxy.directive +``` + +### SSL Configuration + +The haproxy plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. + +#### Enabling letsencrypt integration + +By default, letsencrypt is disabled and https port mappings are ignored. To enable, set the `letsencrypt-email` property with the `--global` flag: + +```shell +dokku haproxy:set --global letsencrypt-email automated@dokku.sh +``` + +After enabling, the Haproxy container will need to be restarted and apps will need to be rebuilt. All http requests will then be redirected to https. + +#### Customizing the letsencrypt server + +The letsencrypt integration is set to the production letsencrypt server by default. To change this, set the `letsencrypt-server` property with the `--global` flag: + +```shell +dokku haproxy:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory +``` + +After enabling, the Haproxy container will need to be restarted and apps will need to be rebuilt to retrieve certificates from the new server. + +## Displaying Haproxy reports for an app + +You can get a report about the app's Haproxy config using the `haproxy:report` command: + +```shell +dokku haproxy:report +``` + +``` +=====> node-js-app haproxy information + Haproxy computed image: byjg/easy-haproxy:4.0.0 + Haproxy computed letsencrypt email: + Haproxy computed letsencrypt server: https://acme-v02.api.letsencrypt.org/directory + Haproxy computed log level: ERROR + Haproxy computed refresh conf: 10 + Haproxy global image: + Haproxy global letsencrypt email: + Haproxy global letsencrypt server: + Haproxy global log level: + Haproxy global refresh conf: +``` + +The `global-` keys hold the raw global value and are empty when nothing has been set globally. The `computed-` keys hold the effective value used at deploy time, falling back to the built-in default when the global value is empty. + +You can run the command for a specific app also. + +```shell +dokku haproxy:report node-js-app +``` + +``` +=====> node-js-app haproxy information + Haproxy computed image: byjg/easy-haproxy:4.0.0 + Haproxy computed letsencrypt email: + Haproxy computed letsencrypt server: https://acme-v02.api.letsencrypt.org/directory + Haproxy computed log level: ERROR + Haproxy computed refresh conf: 10 + Haproxy global image: + Haproxy global letsencrypt email: + Haproxy global letsencrypt server: + Haproxy global log level: + Haproxy global refresh conf: +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku haproxy:report node-js-app --haproxy-computed-image +``` + +## Properties + +### Settable properties + +All haproxy properties are global only. Set with `haproxy:set --global `. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `image` | global only | _parsed from `plugins/haproxy-vhosts/Dockerfile`_ | `--haproxy-global-image`, `--haproxy-computed-image` | Docker image used to run the Haproxy container | +| `letsencrypt-email` | global only | none | `--haproxy-global-letsencrypt-email`, `--haproxy-computed-letsencrypt-email` | Contact email enabling letsencrypt; empty disables https issuance | +| `letsencrypt-server` | global only | `https://acme-v02.api.letsencrypt.org/directory` | `--haproxy-global-letsencrypt-server`, `--haproxy-computed-letsencrypt-server` | ACME directory used when requesting certificates | +| `log-level` | global only | `ERROR` | `--haproxy-global-log-level`, `--haproxy-computed-log-level` | Haproxy log level | +| `refresh-conf` | global only | `10` | `--haproxy-global-refresh-conf`, `--haproxy-computed-refresh-conf` | Seconds between Haproxy polls of the Docker API for label changes | + +### Internal properties + +The following properties are not managed by `haproxy:set` but are recorded internally by the plugin: + +| Property | Description | Source | +|---|---|---| +| `proxy-status` | `started`/`stopped` state of the haproxy compose project | `cmd-haproxy-start`/`cmd-haproxy-stop` in `plugins/haproxy-vhosts/command-functions` | diff --git a/docs/networking/proxies/nginx.md b/docs/networking/proxies/nginx.md new file mode 100644 index 00000000000..70843947fc0 --- /dev/null +++ b/docs/networking/proxies/nginx.md @@ -0,0 +1,681 @@ +# Nginx Proxy + +Dokku uses nginx as its server for routing requests to specific applications. By default, access and error logs are written for each app to `/var/log/nginx/${APP}-access.log` and `/var/log/nginx/${APP}-error.log` respectively + +``` +nginx:access-logs [-t] # Show the nginx access logs for an application (-t follows) +nginx:error-logs [-t] # Show the nginx error logs for an application (-t follows) +nginx:reload # Reloads the nginx server config +nginx:report [] [|--format json] # Displays a nginx report for one or more apps +nginx:set () # Set or clear an nginx property for an app +nginx:show-config # Display app nginx config +nginx:start # Starts the nginx server +nginx:stop # Stops the nginx server +nginx:validate-config [] [--clean] # Validates and optionally cleans up invalid nginx configurations +``` + +## Usage + +> [!WARNING] +> As using multiple proxy plugins on a single Dokku installation can lead to issues routing requests to apps, doing so should be avoided. + +### Request Proxying + +By default, the `web` process is the only process proxied by the nginx proxy implementation. Proxying to other process types may be handled by a custom `nginx.conf.sigil` file, as generally described [below](/docs/networking/proxies/nginx.md#customizing-the-nginx-configuration) + +Nginx will proxy the requests in a [round-robin balancing fashion](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream) to the different deployed (scaled) containers running the `web` proctype. This way, the host's resources can be fully leveraged for single-threaded applications (e.g. `dokku ps:scale node-js-app web=4` on a 4-core machine). + +> [!NOTE] +> Due to how the plugin is implemented, if an app successfully starts up `web` containers but fails to deploy some other containers, nginx may eventually stop routing requests. Users should revert their code in these cases, or manually trigger `dokku proxy:build-config $APP` in order to ensure requests route to the new web containers. + +### Nginx Configuration for Undeployed Apps + +> [!IMPORTANT] +> New as of 0.38.0 + +When an app is created but not yet deployed, has no `web` process type, or has no running web processes, Dokku generates a minimal nginx configuration that returns `502 Bad Gateway` responses. This ensures that: + +- The app's domain resolves and returns a non-200 status code, allowing monitoring tools to detect the issue. +- SSL certificate provisioning tools such as letsencrypt can function, as an nginx server is listening on the domain. +- The `nginx.conf.d/` include directory is available for plugin customization. + +The 502 error page includes auto-retry JavaScript that will automatically reload the page when the application becomes available. + +Once the app is deployed with running `web` processes, the placeholder configuration is automatically replaced with the full proxy configuration. + +### Starting nginx + +> [!IMPORTANT] +> New as of 0.28.0 + +The nginx server can be started via `nginx:start`. + +```shell +dokku nginx:start +``` + +### Stopping nginx + +> [!IMPORTANT] +> New as of 0.28.0 + +The nginx server can be stopped via `nginx:stop`. + +```shell +dokku nginx:stop +``` + +### Reloading nginx + +> [!IMPORTANT] +> New as of 0.38.0 + +The nginx server can be reloaded via `nginx:reload`. This validates the current nginx configuration and, if valid, signals nginx to reload without restarting the running process. This is the preferred command after editing files in `/etc/nginx/conf.d/` directly. + +```shell +dokku nginx:reload +``` + +If the configuration is invalid, the command exits non-zero and prints the validation error without reloading. + +### Checking access logs + +> [!NOTE] +> Changing this value globally or on a per-app basis will require rebuilding the nginx config via the `proxy:build-config` command. + +You may check nginx access logs via the `nginx:access-logs` command. This assumes that app access logs are being stored in `/var/log/nginx/$APP-access.log`, as is the default in the generated `nginx.conf`. + +```shell +dokku nginx:access-logs node-js-app +``` + +You may also follow the logs by specifying the `-t` flag. + +```shell +dokku nginx:access-logs node-js-app -t +``` + +### Checking error logs + +You may check nginx error logs via the `nginx:error-logs` command. This assumes that app error logs are being stored in `/var/log/nginx/$APP-error.log`, as is the default in the generated `nginx.conf`. + +```shell +dokku nginx:error-logs node-js-app +``` + +You may also follow the logs by specifying the `-t` flag. + +```shell +dokku nginx:error-logs node-js-app -t +``` + +### Showing the nginx config + +For debugging purposes, it may be useful to show the nginx config. This can be achieved via the `nginx:show-config` command. + +```shell +dokku nginx:show-config node-js-app +``` + +### Validating nginx configs + +App-supplied `nginx.conf.sigil` files are pre-validated automatically at the start of every deploy, immediately after the template is extracted from the source tree and before the build phase runs. The template is rendered with sigil and run through `nginx -t` against a minimal wrapper config; if validation fails, the deploy is aborted before any build work begins. To bypass this behavior, set `disable-custom-config` to `true` (see "Disabling custom nginx config" below). + +The wrapper config used for validation does not include the top-level `load_module` directives from the global nginx config, so a `nginx.conf.sigil` that relies on a directive from a dynamically loaded module will fail this validation even when `nginx -t` passes against the real server config. See [Custom nginx modules](/docs/appendices/file-formats/nginx-conf-sigil.md#custom-nginx-modules) for how to supply a custom validation wrapper via the `nginx-app-template-source` trigger's `validate-config` template type. + +It may also be desired to validate an nginx config outside of the deployment process. To do so, run the `nginx:validate-config` command. With no arguments, this will validate all app nginx configs, one at a time. A minimal wrapper nginx config is generated for each app's nginx config, upon which `nginx -t` will be run. + +```shell +dokku nginx:validate-config +``` + +As app nginx configs are actually executed within a shared context, it is possible for an individual config to be invalid when being validated standalone but _also_ be valid within the global server context. As such, the exit code for the `nginx:validate-config` command is the exit code of `nginx -t` against the server's real nginx config. + +The `nginx:validate-config` command also takes an optional `--clean` flag. If specified, invalid nginx configs will be removed. + +> [!WARNING] +> Invalid app nginx config's will be removed _even if_ the config is valid in the global server context. + +```shell +dokku nginx:validate-config --clean +``` + +The `--clean` flag may also be specified for a given app: + +```shell +dokku nginx:validate-config node-js-app --clean +``` + +### Displaying nginx reports for an app + +You can get a report about the app's nginx configuration using the `nginx:report` command: + +```shell +dokku nginx:report +``` + +You can run the command for a specific app also. + +```shell +dokku nginx:report node-js-app +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku nginx:report node-js-app --nginx-bind-address-ipv4 +``` + +The `nginx:report` command also takes a `--format` flag, with the valid options including `stdout` (default) and `json`. The `json` output format can be used for automation purposes: + +```shell +dokku nginx:report node-js-app --format json +``` + +The `--format` flag cannot be combined with an info flag. + +### Custom Error Pages + +By default, Dokku provides custom error pages for the following three categories of errors: + +- 4xx: For all non-404 errors with a 4xx response code. +- 404: For "404 Not Found" errors. +- 5xx: For all 5xx error responses + +These are provided as an alternative to the generic Nginx error page, are shared for _all_ applications, and their contents are located on disk at `/var/lib/dokku/data/nginx-vhosts/dokku-errors`. To customize them for a specific app, create a custom `nginx.conf.sigil` as described above and change the paths to point elsewhere. + +### Default site + +> [!IMPORTANT] +> New as of 0.38.0 + +On fresh apt installs, Dokku ships a catch-all default site at `/etc/nginx/conf.d/00-default-vhost.conf` that rejects requests whose Host header (or TLS SNI) does not match any deployed app. The catch-all uses [`ssl_reject_handshake on`](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_reject_handshake) for HTTPS and [`return 444`](https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return) for HTTP. Both close the connection without sending a response. + +The shipped file looks like: + +```nginx +server { + listen 80 default_server; + listen [::]:80 default_server; + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + server_name _; + access_log off; + + ssl_reject_handshake on; + return 444; +} +``` + +The `00-` prefix forces nginx to load this file before `/etc/nginx/conf.d/dokku.conf`, so its `default_server` markers establish the default for each port before any per-app server blocks are loaded. + +On systems running nginx older than 1.19.4 (e.g., Debian Bullseye, which ships nginx 1.18.0), `ssl_reject_handshake` is not available. The postinst detects this and installs an HTTP-only variant instead: + +```nginx +server { + listen 80 default_server; + listen [::]:80 default_server; + + server_name _; + access_log off; + + return 444; +} +``` + +On these systems, HTTPS requests to unknown hosts are not rejected by the catch-all - nginx falls through to the lexicographically first port-443 server block and presents that block's certificate, the same behavior as before 0.38.0. + +#### TLS handshake behavior + +The catch-all does not affect TLS handshakes for legitimate apps. nginx selects the matching server block via SNI before completing the handshake; only requests that have no matching app fall through to the catch-all. + +| Request | Result | +| --- | --- | +| HTTPS to a configured app's hostname (with matching SNI) and the app has a cert | Handshake completes with the app's cert. Catch-all not consulted. | +| HTTPS to a configured app's hostname when the app has no cert configured | Handshake rejected by the catch-all on nginx 1.19.4+. On older nginx, falls through to the lexicographically first port-443 server block and presents its cert, producing a cert-mismatch error. | +| HTTPS to the server's IP with no SNI, or with an SNI matching no app | Handshake rejected on nginx 1.19.4+. On older nginx, falls through to the first port-443 server block. | +| HTTP to a configured app's hostname | Routed normally to that app. Catch-all not consulted. | +| HTTP to a hostname matching no app | Catch-all `return 444`. | + +#### Conflicting upstream nginx default vhost + +A fresh nginx install ships its own default vhost that also claims port 80 with `default_server`, which would cause `nginx -t` to fail with `a duplicate default server for 0.0.0.0:80` once the dokku catch-all is in place. To avoid this, the dokku postinst renames any of the following files in place to `${path}.dokku-disabled` instead of deleting them, preserving any local customizations: + +- `/etc/nginx/sites-enabled/default` +- `/etc/nginx/sites-available/default` +- `/etc/nginx/conf.d/default.conf` + +To recover an original file, inspect the `.dokku-disabled` sibling. + +#### Disabling the catch-all on first install + +Select "No" at the `dokku/install_default_site` debconf prompt, or pre-seed the answer before installing: + +```shell +echo 'dokku dokku/install_default_site boolean false' | debconf-set-selections +``` + +#### Customizing or removing the catch-all after install + +To customize the reject behavior, edit `/etc/nginx/conf.d/00-default-vhost.conf` and reload: + +```shell +dokku nginx:reload +``` + +For example, replace `return 444;` with `return 410;` to respond with an HTTP 410 Gone error page instead of dropping the connection. + +To disable the catch-all without uninstalling dokku: + +```shell +mv /etc/nginx/conf.d/00-default-vhost.conf{,.disabled} +dokku nginx:reload +``` + +#### Installing the catch-all on upgraded or non-apt installs + +Existing dokku installs upgraded from earlier versions do not get the catch-all installed automatically; the upgrade leaves their nginx configuration untouched. To install it manually, copy the shipped template into place and reload nginx: + +```shell +cp /var/lib/dokku/core-plugins/available/nginx-vhosts/templates/default-site.conf /etc/nginx/conf.d/00-default-vhost.conf +dokku nginx:reload +``` + +If the upstream nginx default vhost is also active, disable it first (e.g. `mv /etc/nginx/sites-enabled/default{,.disabled}`) to avoid a duplicate-default-server error. + +#### App-name based alternative + +As an alternative to the catch-all file, you may push an app to your Dokku host with a name like `00-default`. As long as it lists first in `ls /home/dokku/*/nginx.conf | head`, it will be used as the default nginx vhost. + +### Customizing the nginx configuration + +> [!IMPORTANT] +> New as of 0.5.0 + +Dokku uses a templating library by the name of [sigil](https://github.com/gliderlabs/sigil) to generate nginx configuration for each app. This may be overridden by committing the [default configuration template](https://github.com/dokku/dokku/blob/master/plugins/nginx-vhosts/templates/nginx.conf.sigil) to a file named `nginx.conf.sigil`. + +The `nginx.conf.sigil` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `nginx-conf-sigil-path` property: + +```shell +dokku nginx:set node-js-app nginx-conf-sigil-path .dokku/nginx.conf.sigil +``` + +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 `nginx.conf.sigil`. + +The default value may be set by passing an empty value for the option: + +```shell +dokku nginx:set node-js-app nginx-conf-sigil-path +``` + +The `nginx-conf-sigil-path` property can also be set globally. The global default is `nginx.conf.sigil`, and the global value is used when no app-specific value is set. + +```shell +dokku nginx:set --global nginx-conf-sigil-path nginx.conf.sigil +``` + +The default value may be set by passing an empty value for the option. + +```shell +dokku nginx:set --global nginx-conf-sigil-path +``` + +> The [default template](https://github.com/dokku/dokku/blob/master/plugins/nginx-vhosts/templates/nginx.conf.sigil) may change with new releases of Dokku. Please refer to the appropriate template file version for your Dokku version, and make sure to look out for changes when you upgrade. + +#### Disabling custom nginx config + +> [!NOTE] +> Changing this value globally or on a per-app basis will require rebuilding the nginx config via the `proxy:build-config` command. + +While enabled by default, using a custom nginx config can be disabled via `nginx:set`. This may be useful in cases where you do not want to allow users to override any higher-level customization of app nginx config. + +```shell +# enable fetching custom config (default) +dokku nginx:set node-js-app disable-custom-config false + +# disable fetching custom config +dokku nginx:set node-js-app disable-custom-config true +``` + +Unsetting this value is the same as enabling custom nginx config usage. + +#### Available template variables + +``` +{{ .APP }} Application name +{{ .APP_SSL_PATH }} Path to SSL certificate and key +{{ .DOKKU_ROOT }} Global Dokku root directory (ex: app dir would be `{{ .DOKKU_ROOT }}/{{ .APP }}`) +{{ .PROXY_PORT }} Non-SSL nginx listener port (same as the `proxy-port` property) +{{ .PROXY_SSL_PORT }} SSL nginx listener port (same as the `proxy-ssl-port` property) +{{ .NOSSL_SERVER_NAME }} List of non-SSL VHOSTS +{{ .PROXY_PORT_MAP }} List of port mappings (same as the `map` ports property) +{{ .PROXY_UPSTREAM_PORTS }} List of configured upstream ports (derived from the `map` ports property) +{{ .SSL_INUSE }} Boolean set when an app is SSL-enabled +{{ .SSL_SERVER_NAME }} List of SSL VHOSTS +``` + +Finally, each process type has it's network listeners - a list of IP:PORT pairs for the respective app containers - exposed via an `.DOKKU_APP_${PROCESS_TYPE}_LISTENERS` variable - the `PROCESS_TYPE` will be upper-cased with hyphens transformed into underscores. Users can use the new variables to expose non-web processes via the nginx proxy. + +> [!NOTE] +> Application environment variables are available for use in custom templates. To do so, use the form of `{{ var "FOO" }}` to access a variable named `FOO`. + +#### Customizing via configuration files included by the default templates + +The default nginx.conf template will include everything from your apps `nginx.conf.d/` subdirectory in the main `server {}` block (see above): + +``` +include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; +``` + +That means you can put additional configuration in separate files. To increase the client request header timeout, the following can be performed: + +```shell +mkdir /home/dokku/node-js-app/nginx.conf.d/ +echo 'client_header_timeout 50s;' > /home/dokku/node-js-app/nginx.conf.d/timeout.conf +chown dokku:dokku /home/dokku/node-js-app/nginx.conf.d/upload.conf +service nginx reload +``` + +The example above uses additional configuration files directly on the Dokku host. Unlike the `nginx.conf.sigil` file, these additional files will not be copied over from your application repo, and thus need to be placed in the `/home/dokku/node-js-app/nginx.conf.d/` directory manually. + +For PHP Buildpack users, you will also need to provide a `Procfile` and an accompanying `nginx.conf` file to customize the nginx config _within_ the container. The following are example contents for your `Procfile` + +``` +web: vendor/bin/heroku-php-nginx -C nginx.conf -i php.ini php/ +``` + +Your `nginx.conf` file - not to be confused with Dokku's `nginx.conf.sigil` - would also need to be configured as shown in this example: + +``` +client_header_timeout 50s; +location / { + index index.php; + try_files $uri $uri/ /index.php$is_args$args; +} +``` + +Please adjust the `Procfile` and `nginx.conf` file as appropriate. + +### Setting Properties for the nginx config + +The nginx plugin exposes a variety of properties configurable via the `nginx:set` command. The properties are used to configure the generated `nginx.conf` file from the `nginx.conf.sigil` template. The value precedence is app-specific, then global, and finally the Dokku default. + +The nginx:set command takes an app name or the `--global` flag. + +```shell +# set a property for the node-js-app +dokku nginx:set node-js-app property-name some-value + +# set a property globally +dokku nginx:set --global property-name some-value +``` + +Additionally, setting an empty value will result in reverting the value back to it's default. For app-specific values, this means that Dokku will revert to the globally specified (or global default) value. + +```shell +# default the value back to the global value for node-js-app +dokku nginx:set node-js-app property-name + +# use the dokku default as the global value +dokku nginx:set --global property-name +``` + +Changing these value globally or on a per-app basis will require rebuilding the nginx config via the `proxy:build-config` command. + +> [!WARNING] +> Validation is not performed against the values, and they are used as is within Dokku. + +| Property | Default | Type | Explanation | +| --------------------------|---------------------------------------|---------|-------------------------------------------------------------------------------------| +| access-log-format | empty string | string | Name of custom log format to use (log format must be specified elsewhere) | +| access-log-path | `${NGINX_LOG_ROOT}/${APP}-access.log` | string | Log path for nginx access logs (set to `off` to disable) | +| bind-address-ipv4 | `0.0.0.0` | string | Default IPv4 address to bind to | +| bind-address-ipv6 | `[::]` | string | Default IPv6 address to bind to | +| client-max-body-size | `1m` | string | Size (with units) of client request body (usually modified for file uploads) | +| client-body-timeout | `60s` | string | Timeout (with units) for reading the client request body | +| client-header-timeout | `60s` | string | Timeout (with units) for reading the client request headers | +| error-log-path | `${NGINX_LOG_ROOT}/${APP}-error.log` | string | Log path for nginx error logs (set to `off` to disable) | +| hsts | `true` | boolean | Enables or disables HSTS for your application | +| hsts-include-subdomains | `true` | boolean | Forces the browser to apply the HSTS policy to all app subdomains | +| hsts-max-age | `15724800` | integer | Time in seconds to cache HSTS configuration | +| hsts-preload | `false` | boolean | Tells the browser to include the domain in their HSTS preload lists | +| keepalive-timeout | `75s` | string | Timeout (with units) during which a keep-alive client connection will stay open on the server side | +| lingering-timeout | `5s` | string | Timeout (with units) is the maximum waiting time for more client data to arrive | +| nginx-conf-sigil-path | `nginx.conf.sigil` | string | Path in the repository to the `nginx.conf.sigil` file | +| nginx-service-command | | string | Full path to an executable script or binary to invoke instead of the system's init process when interacting with nginx | +| proxy-buffer-size | `8k` (# is os pagesize) | string | Size of the buffer used for reading the first part of proxied server response | +| proxy-buffering | `on` | string | Enables or disables buffering of responses from the proxied server | +| proxy-buffers | `8 8k` | string | Number and size of the buffers used for reading the proxied server response, for a single connection | +| proxy-busy-buffers-size | `16k` | string | Limits the total size of buffers that can be busy sending a response to the client while the response is not yet fully read. | +| proxy-connect-timeout | `60s` | string | Timeout (with units) for establishing a connection to your backend server | +| proxy-keepalive | empty (disabled) | integer | Number of idle keepalive connections to upstream servers (disabled by default) | +| proxy-read-timeout | `60s` | string | Timeout (with units) for reading response from your backend server | +| proxy-send-timeout | `60s` | string | Timeout (with units) for transmitting a request to your backend server | +| send-timeout | `60s` | string | Timeout (with units) for transmitting a response to your the client | +| underscore-in-headers | `off` | string | Enables or disables the use of underscores in client request header fields. | +| x-forwarded-for-value | `$remote_addr` | string | Used for specifying the header value to set for the `X-Forwarded-For` header | +| x-forwarded-port-value | `$server_port` | string | Used for specifying the header value to set for the `X-Forwarded-Port` header | +| x-forwarded-proto-value | `$scheme` | string | Used for specifying the header value to set for the `X-Forwarded-Proto` header | +| x-forwarded-ssl | empty string | string | Less commonly used alternative to `X-Forwarded-Proto` (valid values: `on` or `off`) | + +#### Binding to specific addresses + +> [!NOTE] +> Users with apps that contain a custom `nginx.conf.sigil` file will need to modify the files to respect the new `NGINX_BIND_ADDRESS_IPV4` and `NGINX_BIND_ADDRESS_IPV6` variables. + +Properties: + +- `bind-address-ipv4` +- `bind-address-ipv6` + +This is useful in cases where the proxying should be internal to a network or if there are multiple network interfaces that should respond with different content. + +#### HSTS Header + +> [!WARNING] +> if you enable the header and a subsequent deploy of your application results in an HTTP deploy (for whatever reason), the way the header works means that a browser will not attempt to request the HTTP version of your site if the HTTPS version fails until the max-age is reached. + +Properties: + +- `hsts` +- `hsts-include-subdomains` +- `hsts-max-age` +- `hsts-preload` + +If SSL certificates are present, HSTS will be automatically enabled. + +#### Running behind another proxy — configuring `X-Forwarded-*` headers + +> [!WARNING] +> These values should only be modified if there is an intermediate Load balancer or CDN between the user and the Dokku server hosting your application. + +Properties: + +- `x-forwarded-for-value` +- `x-forwarded-port-value` +- `x-forwarded-proto-value` +- `x-forwarded-ssl` + +Dokku's default Nginx configuration passes the de-facto standard HTTP headers [`X-Forwarded-For`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For), [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto), and `X-Forwarded-Port` to your application. +These headers indicate the IP address of the original client making the request, the protocol of the original request (HTTP or HTTPS), and the port number of the original request, respectively. + +If you have another HTTP proxy sitting in between the end user and your server (for example, a load balancer, or a CDN), then the values of these headers will contain information about (e.g. the IP address of) the closest proxy, and not the end user. + +To fix this, assuming that the other proxy also passes `X-Forwarded-*` headers, which in turn contain information about the end user, you can tell Nginx include those values in the `X-Forwarded-*` headers that it sends to your application. You can do this via `nginx:set`, like so: + +```shell +dokku nginx:set node-js-app x-forwarded-for-value '$http_x_forwarded_for' +dokku nginx:set node-js-app x-forwarded-port-value '$http_x_forwarded_port' +dokku nginx:set node-js-app x-forwarded-proto-value '$http_x_forwarded_proto' +``` + +However, note that you should only do this if: + +1. Requests to your website always go through a trusted proxy. +2. That proxy is configured to send the aforementioned `X-Forwarded-*` headers. + +Otherwise, if it's possible for clients to make HTTP requests directly against your server, bypassing the other proxy, or if the other proxy is not configured to set these headers, then a client can basically pass any arbitrary values for these headers (which your app then presumably reads) and thereby fake an IP address, for example. + +There's also the `X-Forwarded-Ssl` header which a less common alternative to `X-Forwarded-Proto` — and because of that, isn't included in Dokku's default Nginx configuration. It can be turned `on` if need be: + +```shell +# force-setting value to `on` +dokku nginx:set node-js-app x-forwarded-ssl on +``` + +#### Changing log path + +> [!WARNING] +> The defaults should not be changed without verifying that the paths will be writeable by nginx. + +Properties: + +- `access-log-path` +- `error-log-path` + +These setting can be useful for enabling or disabling logging by setting the values to `off`. + +```shell +dokku nginx:set node-js-app access-log-path off +dokku nginx:set node-js-app error-log-path off +``` + +#### Changing log format + +Properties: + +- `access-log-format` + +Prior to changing the log-format, log formats should be specified at a file such as `/etc/nginx/conf.d/00-log-formats.conf`. This will ensure they are available within your app's nginx context. For instance, the following may be added to the above file. It only needs to be specified once to be used for all apps. + +```nginx +# /etc/nginx/conf.d/00-log-formats.conf +# escape=json was added in nginx 1.11.8 +log_format json_combined escape=json + '{' + '"time_local":"$time_local",' + '"remote_addr":"$remote_addr",' + '"remote_user":"$remote_user",' + '"request":"$request",' + '"status":"$status",' + '"body_bytes_sent":"$body_bytes_sent",' + '"request_time":"$request_time",' + '"http_referrer":"$http_referer",' + '"http_user_agent":"$http_user_agent"' + '}'; +``` + +#### Specifying a read timeout + +> [!NOTE] +> All numeric values _must_ have a trailing time value specified (`s` for seconds, `m` for minutes). + +Properties: + +- `proxy-read-timeout` + +#### Specifying a custom client_max_body_size + +> [!NOTE] +> All numerical values _must_ have a trailing size unit specified (`k` for kilobytes, `m` for megabytes). + +Properties: + +- `client-max-body-size` + +This property is commonly used to increase the max file upload size. + +Changing this value when using the PHP buildpack (or any other buildpack that uses an intermediary server) will require changing the value in the server config shipped with that buildpack. Consult your buildpack documentation for further details. + +## Other + +### Domains plugin + +See the [domain configuration documentation](/docs/configuration/domains.md) for more information on how to configure domains for your app. + +### Customizing hostnames + +See the [customizing hostnames documentation](/docs/configuration/domains.md#customizing-hostnames) for more information on how to configure domains for your app. + +### Disabling VHOSTS + +See the [enabling and disabling vhosts documentation](/docs/configuration/domains.md#enabling-and-disabling-vhosts) for more information on how to disable domain usage for your app. + +### SSL Configuration + +See the [ssl documentation](/docs/configuration/ssl.md) for more information on how to configure SSL certificates for your application. + +### Disabling Nginx + +See the [proxy documentation](/docs/networking/proxy-management.md) for more information on how to disable nginx as the proxy implementation for your app. + +### Managing Proxy Port mappings + +See the [ports documentation](/docs/networking/port-management.md) for more information on how to manage ports proxied for your app. + +### Regenerating nginx config + +See the [proxy documentation](/docs/networking/proxy-management.md#regenerating-proxy-config) for more information on how to rebuild the nginx proxy configuration for your app. + +## Properties + +### Settable properties + +All nginx-vhosts properties are settable at both the app and global scope. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `access-log-format` | app + global | none | `--nginx-access-log-format`, `--nginx-global-access-log-format`, `--nginx-computed-access-log-format` | Custom nginx `log_format` directive used for the access log | +| `access-log-path` | app + global | _`{nginx-log-root}/{app}-access.log`_ | `--nginx-access-log-path`, `--nginx-global-access-log-path`, `--nginx-computed-access-log-path` | Path inside the nginx container where access logs are written | +| `bind-address-ipv4` | app + global | none | `--nginx-bind-address-ipv4`, `--nginx-global-bind-address-ipv4`, `--nginx-computed-bind-address-ipv4` | IPv4 address the nginx server block binds to | +| `bind-address-ipv6` | app + global | `::` | `--nginx-bind-address-ipv6`, `--nginx-global-bind-address-ipv6`, `--nginx-computed-bind-address-ipv6` | IPv6 address the nginx server block binds to | +| `client-body-timeout` | app + global | `60s` | `--nginx-client-body-timeout`, `--nginx-global-client-body-timeout`, `--nginx-computed-client-body-timeout` | Time allowed to read the request body from the client | +| `client-header-timeout` | app + global | `60s` | `--nginx-client-header-timeout`, `--nginx-global-client-header-timeout`, `--nginx-computed-client-header-timeout` | Time allowed to read the request header from the client | +| `client-max-body-size` | app + global | `1m` | `--nginx-client-max-body-size`, `--nginx-global-client-max-body-size`, `--nginx-computed-client-max-body-size` | Maximum allowed request body size | +| `disable-custom-config` | app + global | `false` | `--nginx-disable-custom-config`, `--nginx-global-disable-custom-config`, `--nginx-computed-disable-custom-config` | When `true`, ignores app-supplied `nginx.conf.d/*.conf` snippets | +| `error-log-path` | app + global | _`{nginx-log-root}/{app}-error.log`_ | `--nginx-error-log-path`, `--nginx-global-error-log-path`, `--nginx-computed-error-log-path` | Path inside the nginx container where error logs are written | +| `hsts` | app + global | `true` | `--nginx-hsts`, `--nginx-global-hsts`, `--nginx-computed-hsts` | When `true`, emits a `Strict-Transport-Security` header on HTTPS responses | +| `hsts-include-subdomains` | app + global | `true` | `--nginx-hsts-include-subdomains`, `--nginx-global-hsts-include-subdomains`, `--nginx-computed-hsts-include-subdomains` | Adds the `includeSubDomains` directive to the HSTS header | +| `hsts-max-age` | app + global | `15724800` | `--nginx-hsts-max-age`, `--nginx-global-hsts-max-age`, `--nginx-computed-hsts-max-age` | `max-age` value (seconds) in the HSTS header | +| `hsts-preload` | app + global | `false` | `--nginx-hsts-preload`, `--nginx-global-hsts-preload`, `--nginx-computed-hsts-preload` | Adds the `preload` directive to the HSTS header | +| `keepalive-timeout` | app + global | `75s` | `--nginx-keepalive-timeout`, `--nginx-global-keepalive-timeout`, `--nginx-computed-keepalive-timeout` | Time an idle keep-alive connection stays open | +| `lingering-timeout` | app + global | `5s` | `--nginx-lingering-timeout`, `--nginx-global-lingering-timeout`, `--nginx-computed-lingering-timeout` | Time nginx waits for more client data when closing a connection | +| `nginx-conf-sigil-path` | app + global | `nginx.conf.sigil` | `--nginx-nginx-conf-sigil-path`, `--nginx-global-nginx-conf-sigil-path`, `--nginx-computed-nginx-conf-sigil-path` | Path within the app to a custom `nginx.conf.sigil` template | +| `nginx-service-command` | app + global | none | `--nginx-nginx-service-command`, `--nginx-global-nginx-service-command`, `--nginx-computed-nginx-service-command` | Override command used by `nginx:start`/`nginx:stop`/`nginx:reload` | +| `proxy-buffer-size` | app + global | _system pagesize_ | `--nginx-proxy-buffer-size`, `--nginx-global-proxy-buffer-size`, `--nginx-computed-proxy-buffer-size` | Buffer size for reading the first part of the upstream response | +| `proxy-buffering` | app + global | `on` | `--nginx-proxy-buffering`, `--nginx-global-proxy-buffering`, `--nginx-computed-proxy-buffering` | Whether nginx buffers upstream responses (`on` or `off`) | +| `proxy-buffers` | app + global | _`8 {pagesize}`_ | `--nginx-proxy-buffers`, `--nginx-global-proxy-buffers`, `--nginx-computed-proxy-buffers` | Number and size of buffers used for an upstream response | +| `proxy-busy-buffers-size` | app + global | _`2 * pagesize`_ | `--nginx-proxy-busy-buffers-size`, `--nginx-global-proxy-busy-buffers-size`, `--nginx-computed-proxy-busy-buffers-size` | Maximum buffer size that can be busy sending a response to the client | +| `proxy-connect-timeout` | app + global | `60s` | `--nginx-proxy-connect-timeout`, `--nginx-global-proxy-connect-timeout`, `--nginx-computed-proxy-connect-timeout` | Time to establish a connection to the upstream | +| `proxy-keepalive` | app + global | none | `--nginx-proxy-keepalive`, `--nginx-global-proxy-keepalive`, `--nginx-computed-proxy-keepalive` | Number of idle keep-alive connections to upstream servers held open per worker | +| `proxy-read-timeout` | app + global | `60s` | `--nginx-proxy-read-timeout`, `--nginx-global-proxy-read-timeout`, `--nginx-computed-proxy-read-timeout` | Time to read a response from the upstream | +| `proxy-send-timeout` | app + global | `60s` | `--nginx-proxy-send-timeout`, `--nginx-global-proxy-send-timeout`, `--nginx-computed-proxy-send-timeout` | Time to transmit a request to the upstream | +| `send-timeout` | app + global | `60s` | `--nginx-send-timeout`, `--nginx-global-send-timeout`, `--nginx-computed-send-timeout` | Time between two successive write operations to the client | +| `underscore-in-headers` | app + global | `off` | `--nginx-underscore-in-headers`, `--nginx-global-underscore-in-headers`, `--nginx-computed-underscore-in-headers` | Whether to allow underscores in client request header field names | +| `x-forwarded-for-value` | app + global | `$remote_addr` | `--nginx-x-forwarded-for-value`, `--nginx-global-x-forwarded-for-value`, `--nginx-computed-x-forwarded-for-value` | Value used for the `X-Forwarded-For` header | +| `x-forwarded-port-value` | app + global | `$server_port` | `--nginx-x-forwarded-port-value`, `--nginx-global-x-forwarded-port-value`, `--nginx-computed-x-forwarded-port-value` | Value used for the `X-Forwarded-Port` header | +| `x-forwarded-proto-value` | app + global | `$scheme` | `--nginx-x-forwarded-proto-value`, `--nginx-global-x-forwarded-proto-value`, `--nginx-computed-x-forwarded-proto-value` | Value used for the `X-Forwarded-Proto` header | +| `x-forwarded-ssl` | app + global | none | `--nginx-x-forwarded-ssl`, `--nginx-global-x-forwarded-ssl`, `--nginx-computed-x-forwarded-ssl` | Value used for the `X-Forwarded-Ssl` header (e.g. `on`/`off`) | + +### Read-only flags + +The following flag surfaces in `nginx:report` but is not managed by `nginx:set`: + +| Flag | Description | +|---|---| +| `--nginx-last-visited-at` | UNIX timestamp of the last request served by nginx for the app | + +### Internal properties + +The following properties are recorded internally by the plugin and are not exposed via `nginx:report`: + +| Property | Description | Source | +|---|---|---| +| `proxy-status` | `started`/`stopped` state of the nginx system service | `cmd-nginx-start`/`cmd-nginx-stop` | +| `nginx-conf-sigil-migrated` | Migration sentinel set the first time a v1 template is rewritten to the current schema | `plugins/nginx-vhosts/install` writes `"true"` once the install-time migration runs | diff --git a/docs/networking/proxies/openresty.md b/docs/networking/proxies/openresty.md new file mode 100644 index 00000000000..ea8d0272b46 --- /dev/null +++ b/docs/networking/proxies/openresty.md @@ -0,0 +1,326 @@ +# OpenResty Proxy + +> [!IMPORTANT] +> New as of 0.31.0 + +Dokku can provide integration with the [OpenResty](https://openresty.org/) proxy service by utilizing the Docker label-based integration implemented by [openresty-docker-proxy](https://github.com/dokku/openresty-docker-proxy). + +``` +openresty:report [] [] # Displays a openresty report for one or more apps +openresty:logs [--num num] [--tail] # Display openresty log output +openresty:set () # Set or clear an openresty property for an app +openresty:show-config # Display openresty compose config +openresty:start # Starts the openresty server +openresty:stop # Stops the openresty server +``` + +## Requirements + +Using the `openresty` plugin integration requires the `docker-compose-plugin` for Docker. See [this document](https://docs.docker.com/compose/install/) from the Docker documentation for more information on the installation process for the `docker-compose-plugin`. + +## Usage + +> [!WARNING] +> As using multiple proxy plugins on a single Dokku installation can lead to issues routing requests to apps, doing so should be avoided. As the default proxy implementation is nginx, users are encouraged to stop the nginx service before switching to OpenResty. + +The OpenResty plugin has specific rules for routing requests: + +- OpenResty integration is exposed via docker labels attached to containers. Changes in labels require either app deploys or rebuilds. +- While OpenResty will respect labels associated with other containers, only `web` containers have OpenResty labels injected by the plugin. +- Only `http:80` and `https:443` port mappings are supported at this time. +- Requests are routed as soon as the container is running and passing healthchecks. + +### Switching to OpenResty + +To use the OpenResty plugin, use the `proxy:set` command for the app in question: + +```shell +dokku proxy:set node-js-app type openresty +``` + +This will enable the docker label-based OpenResty integration. All future deploys will inject the correct labels for OpenResty to read and route requests to containers. Due to the docker label-based integration used by OpenResty, a single deploy or rebuild will be required before requests will route successfully. + +```shell +dokku ps:rebuild node-js-app +``` + +Any changes to domains or port mappings will also require either a deploy or rebuild. + +### Starting OpenResty container + +OpenResty can be started via the `openresty:start` command. This will start a OpenResty container via the `docker compose up` command. + +```shell +dokku openresty:start +``` + +### Stopping the OpenResty container + +OpenResty may be stopped via the `openresty:stop` command. + +```shell +dokku openresty:stop +``` + +The OpenResty container will be stopped and removed from the system. If the container is not running, this command will do nothing. + +### Showing the OpenResty compose config + +For debugging purposes, it may be useful to show the OpenResty compose config. This can be achieved via the `openresty:show-config` command. + +```shell +dokku openresty:show-config +``` + +### Customizing the OpenResty container image + +While the default OpenResty image is hardcoded, users may specify an alternative by setting the `image` property with the `--global` flag: + +```shell +dokku openresty:set --global image dokku/openresty-docker-proxy:0.5.6 +``` + +### Checking the OpenResty container's logs + +It may be necessary to check the OpenResty container's logs to ensure that OpenResty is operating as expected. This can be performed with the `openresty:logs` command. + +```shell +dokku openresty:logs +``` + +This command also supports the following modifiers: + +```shell +--num NUM # the number of lines to display +--tail # continually stream logs +``` + +You can use these modifiers as follows: + +```shell +dokku openresty:logs --tail --num 10 +``` + +The above command will show logs continually from the openresty container, with an initial history of 10 log lines + +### Customizing Openresty Settings for an app + +#### OpenResty Properties + +The OpenResty plugin supports all properties supported by the `nginx:set` command via `openresty:set`. At this time, please consult the nginx documentation for more information on what properties are available. + +Please note that the oldest running container will be used for OpenResty configuration, and thus newer config may not apply until older app containers are retired during/after a deploy, depending on your zero-downtime settings. + +#### Custom OpenResty Templates + +At this time, the OpenResty plugin does not allow complete customization of the template used to manage an app's vhost. Apps will use a template provided by the OpenResty container to proxy requests. See the next section for documentation on how to configure portions of the template. + +#### Injecting custom snippets into the OpenResty config + +The OpenResty plugin allows users to specify templates in their repository for auto-injection into the OpenResty config. Please note that this configuration should be validated prior to deployment or may cause outages in your OpenResty proxy layer. + +The following folders within an app repository may have `*.conf` files that will be automatically injected into the OpenResty config. + +- `openresty/http-includes/`: Injected in the `server` block serving http(s) requests for the app. +- `openresty/http-location-includes/`: Injected in the `location` block that proxies to the app in the app's respective `server` block. + +Custom snippets filenames may only include alphanumeric, underscore, and dot characters. For security reasons, filenames that contain other characters will be ignored. + +### Label Management + +The OpenResty plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure OpenResty behavior beyond what the plugin provides by default. + +Refer to the upstream [openresty-docker-proxy](https://github.com/dokku/openresty-docker-proxy) documentation for more information on what labels are available. + +#### Adding a label + +To add a custom container label to an app, use the `openresty:labels:add` command: + +```shell +dokku openresty:labels:add node-js-app openresty.directive value +``` + +This will add the label `openresty.directive=value` to the app's containers. After adding a label, you will need to rebuild or redeploy the app for the label to be applied to running containers. + +```shell +dokku ps:rebuild node-js-app +``` + +#### Removing a label + +To remove a custom container label from an app, use the `openresty:labels:remove` command: + +```shell +dokku openresty:labels:remove node-js-app openresty.directive +``` + +This will remove the specified label from the app. After removing a label, you will need to rebuild or redeploy the app for the change to be applied to running containers. + +```shell +dokku ps:rebuild node-js-app +``` + +#### Showing labels + +To view all custom container labels for an app, use the `openresty:labels:show` command: + +```shell +dokku openresty:labels:show node-js-app +``` + +To view a specific label value, provide the label name: + +```shell +dokku openresty:labels:show node-js-app openresty.directive +``` + +### SSL Configuration + +The OpenResty plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. + +#### Enabling letsencrypt integration + +By default, letsencrypt is disabled and https port mappings are ignored. To enable, set the `letsencrypt-email` property with the `--global` flag: + +```shell +dokku openresty:set --global letsencrypt-email automated@dokku.sh +``` + +After enabling, the OpenResty container will need to be restarted and apps will need to be rebuilt. All http requests will then be redirected to https. + +#### Customizing the letsencrypt server + +The letsencrypt integration is set to the production letsencrypt server by default. To change this, set the `letsencrypt-server` property with the `--global` flag: + +```shell +dokku openresty:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory +``` + +After enabling, the OpenResty container will need to be restarted and apps will need to be rebuilt to retrieve certificates from the new server. + +#### Limiting letsencrypt to certain domains + +> [!WARNING] +> Changing this value may cause OpenResty to fail to start if the value is not valid. Caution should be exercised when changing this value from the defaults. + +In cases where your server's IP may have invalid domains pointing at it, limiting letsencrypt to certain allowed domains may be desirable to reduce spam requests on the Letsencrypt servers. The default is to allow all domains to have certificates retrieved, but this can be limited by specifying the `allowed-letsencrypt-domains-func-base64` global property. + +The default internal value for `allowed-letsencrypt-domains-func-base64` is the base64 representation of `return true`, and is meant to be the body of a lua function that return a boolean value. + +```shell +value="$(echo 'return true' | base64 -w 0)" +dokku openresty:set --global allowed-letsencrypt-domains-func-base64 $value +``` + +As this is a global value, once changed, OpenResty should be stopped and started again for the value to take effect: + +```shell +dokku openresty:stop +dokku openresty:start +``` + +A more complex example would be to limit provisioning of certificates to domains in a specific list. The body of the lua function has access to a variable `domain`, and we can use it like so: + +```shell +body='allowed_domains = {"domain.com", "extra-domain.com"} + +for index, value in ipairs(allowed_domains) do + if value == domain then + return true + end +end + +return false +' +value="$(echo "$body" | base64 -w 0)" +dokku openresty:set --global allowed-letsencrypt-domains-func-base64 $value +``` + +To reset the value to the default, simply specify a blank value prior to restarting OpenResty: + +```shell +dokku openresty:set --global allowed-letsencrypt-domains-func-base64 +``` + +## Displaying OpenResty reports for an app + +You can get a report about the app's OpenResty config using the `openresty:report` command: + +```shell +dokku openresty:report +``` + +``` +=====> node-js-app openresty information + Openresty image: dokku/openresty-docker-proxy:0.5.6 + Openresty letsencrypt email: automated@dokku.sh +=====> python-app openresty information + Openresty image: dokku/openresty-docker-proxy:0.5.6 + Openresty letsencrypt email: automated@dokku.sh +=====> ruby-app openresty information + Openresty image: dokku/openresty-docker-proxy:0.5.6 + Openresty letsencrypt email: automated@dokku.sh +``` + +You can run the command for a specific app also. + +```shell +dokku openresty:report node-js-app +``` + +``` +=====> node-js-app openresty information + Openresty image: dokku/openresty-docker-proxy:0.5.6 + Openresty letsencrypt email: automated@dokku.sh +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku openresty:report node-js-app --openresty-computed-letsencrypt-email +``` + +## Properties + +### Settable properties + +Five properties (`image`, `log-level`, `letsencrypt-email`, `letsencrypt-server`, `allowed-letsencrypt-domains-func-base64`) are global only. The rest may be set per-app or globally with `--global`; a global value applies to any app that has no per-app value, otherwise the built-in default is used. + +Global-only properties expose two report flags: `--openresty-global-` returns the raw stored value (empty when the property has never been set), while `--openresty-computed-` returns the effective value (the global value if set, otherwise the built-in default). + +App-or-global properties expose three report flags: `--openresty-` returns the raw per-app value (empty when unset), `--openresty-global-` returns the raw global value (empty when unset), and `--openresty-computed-` returns the effective value, resolving the per-app value first, then the global value, then the built-in default. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `access-log-format` | app or global | none | `--openresty-access-log-format`, `--openresty-global-access-log-format`, `--openresty-computed-access-log-format` | Custom nginx `log_format` directive used for the access log | +| `access-log-path` | app or global | _`/var/log/nginx/{app}-access.log`_ | `--openresty-access-log-path`, `--openresty-global-access-log-path`, `--openresty-computed-access-log-path` | Path inside the openresty container where access logs are written | +| `allowed-letsencrypt-domains-func-base64` | global only | _allow-all stub_ | `--openresty-global-allowed-letsencrypt-domains-func-base64`, `--openresty-computed-allowed-letsencrypt-domains-func-base64` | Base64-encoded Lua function deciding which domains may request a letsencrypt certificate | +| `bind-address-ipv4` | app or global | none | `--openresty-bind-address-ipv4`, `--openresty-global-bind-address-ipv4`, `--openresty-computed-bind-address-ipv4` | IPv4 address the openresty server block binds to | +| `bind-address-ipv6` | app or global | `::` | `--openresty-bind-address-ipv6`, `--openresty-global-bind-address-ipv6`, `--openresty-computed-bind-address-ipv6` | IPv6 address the openresty server block binds to | +| `client-body-timeout` | app or global | `60s` | `--openresty-client-body-timeout`, `--openresty-global-client-body-timeout`, `--openresty-computed-client-body-timeout` | Time allowed to read the request body from the client | +| `client-header-timeout` | app or global | `60s` | `--openresty-client-header-timeout`, `--openresty-global-client-header-timeout`, `--openresty-computed-client-header-timeout` | Time allowed to read the request header from the client | +| `client-max-body-size` | app or global | `1m` | `--openresty-client-max-body-size`, `--openresty-global-client-max-body-size`, `--openresty-computed-client-max-body-size` | Maximum allowed request body size | +| `error-log-path` | app or global | _`/var/log/nginx/{app}-error.log`_ | `--openresty-error-log-path`, `--openresty-global-error-log-path`, `--openresty-computed-error-log-path` | Path inside the openresty container where error logs are written | +| `hsts` | app or global | `true` | `--openresty-hsts`, `--openresty-global-hsts`, `--openresty-computed-hsts` | When `true`, emits a `Strict-Transport-Security` header on HTTPS responses | +| `hsts-include-subdomains` | app or global | `true` | `--openresty-hsts-include-subdomains`, `--openresty-global-hsts-include-subdomains`, `--openresty-computed-hsts-include-subdomains` | Adds the `includeSubDomains` directive to the HSTS header | +| `hsts-max-age` | app or global | `15724800` | `--openresty-hsts-max-age`, `--openresty-global-hsts-max-age`, `--openresty-computed-hsts-max-age` | `max-age` value (seconds) in the HSTS header | +| `hsts-preload` | app or global | `false` | `--openresty-hsts-preload`, `--openresty-global-hsts-preload`, `--openresty-computed-hsts-preload` | Adds the `preload` directive to the HSTS header | +| `image` | global only | _parsed from `plugins/openresty-vhosts/Dockerfile`_ | `--openresty-global-image`, `--openresty-computed-image` | Docker image used to run the openresty container | +| `keepalive-timeout` | app or global | `75s` | `--openresty-keepalive-timeout`, `--openresty-global-keepalive-timeout`, `--openresty-computed-keepalive-timeout` | Time an idle keep-alive connection stays open | +| `letsencrypt-email` | global only | none | `--openresty-global-letsencrypt-email`, `--openresty-computed-letsencrypt-email` | Contact email enabling letsencrypt; empty disables https issuance | +| `letsencrypt-server` | global only | `https://acme-v02.api.letsencrypt.org/directory` | `--openresty-global-letsencrypt-server`, `--openresty-computed-letsencrypt-server` | ACME directory used when requesting certificates | +| `lingering-timeout` | app or global | `5s` | `--openresty-lingering-timeout`, `--openresty-global-lingering-timeout`, `--openresty-computed-lingering-timeout` | Time openresty waits for more client data when closing a connection | +| `log-level` | global only | `ERROR` | `--openresty-global-log-level`, `--openresty-computed-log-level` | Openresty log level | +| `proxy-buffer-size` | app or global | _system pagesize_ | `--openresty-proxy-buffer-size`, `--openresty-global-proxy-buffer-size`, `--openresty-computed-proxy-buffer-size` | Buffer size for reading the first part of the upstream response | +| `proxy-buffering` | app or global | `on` | `--openresty-proxy-buffering`, `--openresty-global-proxy-buffering`, `--openresty-computed-proxy-buffering` | Whether openresty buffers upstream responses (`on` or `off`) | +| `proxy-buffers` | app or global | _`8 {pagesize}`_ | `--openresty-proxy-buffers`, `--openresty-global-proxy-buffers`, `--openresty-computed-proxy-buffers` | Number and size of buffers used for an upstream response | +| `proxy-busy-buffers-size` | app or global | _`2 * pagesize`_ | `--openresty-proxy-busy-buffers-size`, `--openresty-global-proxy-busy-buffers-size`, `--openresty-computed-proxy-busy-buffers-size` | Maximum buffer size that can be busy sending a response to the client | +| `proxy-connect-timeout` | app or global | `60s` | `--openresty-proxy-connect-timeout`, `--openresty-global-proxy-connect-timeout`, `--openresty-computed-proxy-connect-timeout` | Time to establish a connection to the upstream | +| `proxy-read-timeout` | app or global | `60s` | `--openresty-proxy-read-timeout`, `--openresty-global-proxy-read-timeout`, `--openresty-computed-proxy-read-timeout` | Time to read a response from the upstream | +| `proxy-send-timeout` | app or global | `60s` | `--openresty-proxy-send-timeout`, `--openresty-global-proxy-send-timeout`, `--openresty-computed-proxy-send-timeout` | Time to transmit a request to the upstream | +| `send-timeout` | app or global | `60s` | `--openresty-send-timeout`, `--openresty-global-send-timeout`, `--openresty-computed-send-timeout` | Time between two successive write operations to the client | +| `underscore-in-headers` | app or global | `off` | `--openresty-underscore-in-headers`, `--openresty-global-underscore-in-headers`, `--openresty-computed-underscore-in-headers` | Whether to allow underscores in client request header field names | +| `x-forwarded-for-value` | app or global | `$remote_addr` | `--openresty-x-forwarded-for-value`, `--openresty-global-x-forwarded-for-value`, `--openresty-computed-x-forwarded-for-value` | Value used for the `X-Forwarded-For` header | +| `x-forwarded-port-value` | app or global | `$server_port` | `--openresty-x-forwarded-port-value`, `--openresty-global-x-forwarded-port-value`, `--openresty-computed-x-forwarded-port-value` | Value used for the `X-Forwarded-Port` header | +| `x-forwarded-proto-value` | app or global | `$scheme` | `--openresty-x-forwarded-proto-value`, `--openresty-global-x-forwarded-proto-value`, `--openresty-computed-x-forwarded-proto-value` | Value used for the `X-Forwarded-Proto` header | +| `x-forwarded-ssl` | app or global | none | `--openresty-x-forwarded-ssl`, `--openresty-global-x-forwarded-ssl`, `--openresty-computed-x-forwarded-ssl` | Value used for the `X-Forwarded-Ssl` header (e.g. `on`/`off`) | diff --git a/docs/networking/proxies/traefik.md b/docs/networking/proxies/traefik.md new file mode 100644 index 00000000000..2d079cc2123 --- /dev/null +++ b/docs/networking/proxies/traefik.md @@ -0,0 +1,425 @@ +# Traefik Proxy + +> [!IMPORTANT] +> New as of 0.28.0 + +Dokku provides integration with the [Traefik](https://traefik.io/) proxy service by utilizing the Docker label-based integration implemented by Traefik. + +``` +traefik:report [] [] # Displays a traefik report for one or more apps +traefik:logs [--num num] [--tail] # Display traefik log output +traefik:set () # Set or clear an traefik property for an app +traefik:show-config # Display traefik compose config +traefik:start # Starts the traefik server +traefik:stop # Stops the traefik server +``` + +## Requirements + +Using the `traefik` plugin integration requires the `docker-compose-plugin` for Docker. See [this document](https://docs.docker.com/compose/install/) from the Docker documentation for more information on the installation process for the `docker-compose-plugin`. + +## Usage + +> [!WARNING] +> As using multiple proxy plugins on a single Dokku installation can lead to issues routing requests to apps, doing so should be avoided. As the default proxy implementation is nginx, users are encouraged to stop the nginx service before switching to Traefik. + +The Traefik plugin has specific rules for routing requests: + +- Traefik integration is exposed via docker labels attached to containers. Changes in labels require either app deploys or rebuilds. +- While Traefik will respect labels associated with other containers, only `web` containers have Traefik labels injected by the plugin. +- Only `http:80` and `https:443` port mappings are supported. +- If no `http:80` mapping is found, the first `http` port mapping is used for http requests. +- If no `https:443` mapping is found, the first `https` port mapping is used for https requests. +- If no `https` mapping is found, the container port from `http:80` will be used for https requests. +- Requests are routed as soon as the container is running and passing healthchecks. +- Readiness healthchecks defined in `app.json` with a `path` property are automatically transformed into Traefik healthcheck labels. + +### Healthchecks + +When an app has a readiness healthcheck defined in its `app.json` file with a `path` property, Dokku automatically generates Traefik healthcheck labels. These labels configure Traefik to perform health checks on the container before routing traffic to it. + +The following `app.json` healthcheck properties are mapped to Traefik labels: + +| app.json Property | Traefik Label Property | Description | +|-------------------|------------------------|-------------| +| `path` | `healthcheck.path` | The HTTP path to check (required) | +| `scheme` | `healthcheck.scheme` | The scheme to use (`http` or `https`) | +| `port` | `healthcheck.port` | The port to check | +| `timeout` | `healthcheck.timeout` | Timeout in seconds (formatted as `Xs`) | +| `wait` | `healthcheck.interval` | Interval between checks in seconds (formatted as `Xs`) | + +Example `app.json` configuration: + +```json +{ + "healthchecks": { + "web": [ + { + "name": "web readiness check", + "path": "/health", + "timeout": 5, + "type": "readiness", + "wait": 10 + } + ] + } +} +``` + +> [!NOTE] +> Only the first readiness healthcheck with a `path` property is used. Liveness, startup, and command-based healthchecks are not transformed into Traefik labels. + +### Switching to Traefik + +To use the Traefik plugin, use the `proxy:set` command for the app in question: + +```shell +dokku proxy:set node-js-app type traefik +``` + +This will enable the docker label-based Traefik integration. All future deploys will inject the correct labels for Traefik to read and route requests to containers. Due to the docker label-based integration used by Traefik, a single deploy or rebuild will be required before requests will route successfully. + +```shell +dokku ps:rebuild node-js-app +``` + +Any changes to domains or port mappings will also require either a deploy or rebuild. + +### Starting Traefik container + +Traefik can be started via the `traefik:start` command. This will start a Traefik container via the `docker compose up` command. + +```shell +dokku traefik:start +``` + +### Stopping the Traefik container + +Traefik may be stopped via the `traefik:stop` command. + +```shell +dokku traefik:stop +``` + +The Traefik container will be stopped and removed from the system. If the container is not running, this command will do nothing. + +### Changing the Traefik entrypoint names + +When you use a self-hosted Traefik instance, your entrypoint names might be different from the default `http` and `https` + +Use `traefik:set` to set both `http-entry-point` and `https-entry-point` to custom values + +```shell +dokku traefik:set --global http-entry-point web +dokku traefik:set --global https-entry-point websecure +``` + +### Showing the Traefik compose config + +For debugging purposes, it may be useful to show the Traefik compose config. This can be achieved via the `traefik:show-config` command. + +```shell +dokku traefik:show-config +``` + +### Customizing the Traefik container image + +While the default Traefik image is hardcoded, users may specify an alternative by setting the `image` property with the `--global` flag: + +```shell +dokku traefik:set --global image traefik:v2.8 +``` + +#### Checking the Traefik container's logs + +It may be necessary to check the Traefik container's logs to ensure that Traefik is operating as expected. This can be performed with the `traefik:logs` command. + +```shell +dokku traefik:logs +``` + +This command also supports the following modifiers: + +```shell +--num NUM # the number of lines to display +--tail # continually stream logs +``` + +You can use these modifiers as follows: + +```shell +dokku traefik:logs --tail --num 10 +``` + +The above command will show logs continually from the traefik container, with an initial history of 10 log lines + +### Changing the Traefik log level + +Traefik log output is set to `ERROR` by default. It may be changed by setting the `log-level` property with the `--global` flag: + +```shell +dokku traefik:set --global log-level DEBUG +``` + +After modifying, the Traefik container will need to be restarted. + +### Label Management + +The Traefik plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure Traefik behavior beyond what the plugin provides by default. + +Refer to the upstream [Traefik](https://doc.traefik.io/traefik/) documentation for more information on what labels are available. + +#### Adding a label + +To add a custom container label to an app, use the `traefik:labels:add` command: + +```shell +dokku traefik:labels:add node-js-app traefik.directive value +``` + +This will add the label `traefik.directive=value` to the app's containers. After adding a label, you will need to rebuild or redeploy the app for the label to be applied to running containers. + +```shell +dokku ps:rebuild node-js-app +``` + +#### Removing a label + +To remove a custom container label from an app, use the `traefik:labels:remove` command: + +```shell +dokku traefik:labels:remove node-js-app traefik.directive +``` + +This will remove the specified label from the app. After removing a label, you will need to rebuild or redeploy the app for the change to be applied to running containers. + +```shell +dokku ps:rebuild node-js-app +``` + +#### Showing labels + +To view all custom container labels for an app, use the `traefik:labels:show` command: + +```shell +dokku traefik:labels:show node-js-app +``` + +To view a specific label value, provide the label name: + +```shell +dokku traefik:labels:show node-js-app traefik.directive +``` + +### SSL Configuration + +The traefik plugin only supports automatic ssl certificates from it's letsencrypt integration. Managed certificates provided by the `certs` plugin are ignored. + +#### Enabling letsencrypt integration + +By default, letsencrypt is disabled and https port mappings are ignored. To enable, set the `letsencrypt-email` property with the `--global` flag: + +```shell +dokku traefik:set --global letsencrypt-email automated@dokku.sh +``` + +After enabling, apps will need to be rebuilt and the Traefik container will need to be restarted. All http requests will then be redirected to https. + +#### Customizing the letsencrypt server + +The letsencrypt integration is set to the production letsencrypt server by default. To change this, set the `letsencrypt-server` property with the `--global` flag: + +```shell +dokku traefik:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory +``` + +After enabling, the Traefik container will need to be restarted and apps will need to be rebuilt to retrieve certificates from the new server. + +#### Switching to DNS-01 challenge mode + +By default, Traefik uses TLS-ALPN-01 challenge for obtaining certificates. To switch to DNS-01 challenge mode (useful for wildcard certificates or when port 443 is not accessible), you need to: + +1. Set the challenge mode to `dns`: + +```shell +dokku traefik:set --global challenge-mode dns +``` + +2. Set your DNS provider: + +```shell +dokku traefik:set --global dns-provider cloudflare +``` + +3. Configure the required environment variables for your DNS provider. Each DNS provider requires specific environment variables. The variable names should be prefixed with `dns-provider-`: + +```shell +dokku traefik:set --global dns-provider-cf_api_email user@example.com +dokku traefik:set --global dns-provider-cf_api_key your-api-key +``` + +The `dns-provider-` prefix will be stripped and the variable name will be uppercased when passed to the Traefik container. For example, `dns-provider-cf_api_email` becomes `CF_API_EMAIL`. + +After configuring, the Traefik container will need to be restarted and apps will need to be rebuilt. + +Refer to the [Traefik DNS Challenge documentation](https://doc.traefik.io/traefik/https/acme/#dnschallenge) for the list of supported DNS providers and their required environment variables. + +To switch back to TLS challenge mode: + +```shell +dokku traefik:set --global challenge-mode tls +``` + +### API Access + +Traefik exposes an API and Dashboard, which Dokku disables by default for security reasons. It can be exposed and customized as described below. + +#### Enabling the api + +> [!WARNING] +> Users enabling the dashboard should also enable api basic auth. + +By default, the api is disabled. To enable, set the `api-enabled` property with the `--global` flag: + +```shell +dokku traefik:set --global api-enabled true +``` + +After enabling, the Traefik container will need to be restarted. + +#### Enabling the dashboard + +> [!WARNING] +> Users enabling the dashboard should also enable api basic auth. + +By default, the dashboard is disabled. To enable, set the `dashboard-enabled` property with the `--global` flag: + +```shell +dokku traefik:set --global dashboard-enabled true +``` + +After enabling, the Traefik container will need to be restarted. + +#### Enabling api basic auth + +Users enabling either the api or dashboard are encouraged to enable basic auth. This will apply _only_ to the api/dashboard, and not to apps. To enable, set the `basic-auth-username` and `basic-auth-password` properties with the `--global` flag:. Both must be set or basic auth will not be enabled. + +```shell +dokku traefik:set --global basic-auth-username username +dokku traefik:set --global basic-auth-password password +``` + +After enabling, the Traefik container will need to be restarted. + +#### Customizing the api hostname + +The hostname used for the api and dashboard is set to `traefik.dokku.me` by default. It can be customized by setting the `api-vhost` property with the `--global` flag: + +```shell +dokku traefik:set --global api-vhost lb.dokku.me +``` + +After enabling, the Traefik container will need to be restarted. + +## Displaying Traefik reports for an app + +You can get a report about the app's Traefik config using the `traefik:report` command: + +```shell +dokku traefik:report +``` + +``` +=====> node-js-app traefik information + Traefik computed api enabled: false + Traefik computed api vhost: traefik.dokku.me + Traefik computed challenge mode: tls + Traefik computed dashboard enabled: false + Traefik computed http entry point: http + Traefik computed https entry point: https + Traefik computed image: traefik:v2.8 + Traefik computed letsencrypt email: + Traefik computed letsencrypt server: https://acme-v02.api.letsencrypt.org/directory + Traefik computed log level: ERROR + Traefik global api enabled: + Traefik global api vhost: + Traefik global challenge mode: + Traefik global dashboard enabled: + Traefik global http entry point: + Traefik global https entry point: + Traefik global image: + Traefik global letsencrypt email: + Traefik global letsencrypt server: + Traefik global log level: +``` + +The `global-` keys hold the raw global value and are empty when nothing has been set globally. The `computed-` keys hold the effective value used at deploy time, falling back to the built-in default when the global value is empty. + +You can run the command for a specific app also. + +```shell +dokku traefik:report node-js-app +``` + +``` +=====> node-js-app traefik information + Traefik computed api enabled: false + Traefik computed api vhost: traefik.dokku.me + Traefik computed challenge mode: tls + Traefik computed dashboard enabled: false + Traefik computed http entry point: http + Traefik computed https entry point: https + Traefik computed image: traefik:v2.8 + Traefik computed letsencrypt email: + Traefik computed letsencrypt server: https://acme-v02.api.letsencrypt.org/directory + Traefik computed log level: ERROR + Traefik global api enabled: + Traefik global api vhost: + Traefik global challenge mode: + Traefik global dashboard enabled: + Traefik global http entry point: + Traefik global https entry point: + Traefik global image: + Traefik global letsencrypt email: + Traefik global letsencrypt server: + Traefik global log level: +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku traefik:report node-js-app --traefik-computed-api-enabled +``` + +## Properties + +### Settable properties + +All traefik properties are global only. Set with `traefik:set --global `. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `api-enabled` | global only | `false` | `--traefik-global-api-enabled`, `--traefik-computed-api-enabled` | When `true`, enables the Traefik HTTP API | +| `api-entry-point` | global only | none | `--traefik-global-api-entry-point`, `--traefik-computed-api-entry-point` | Name of the entry point used by the Traefik API | +| `api-entry-point-address` | global only | none | `--traefik-global-api-entry-point-address`, `--traefik-computed-api-entry-point-address` | Address (`host:port`) the Traefik API listens on | +| `api-vhost` | global only | `traefik.dokku.me` | `--traefik-global-api-vhost`, `--traefik-computed-api-vhost` | Virtual host that routes to the Traefik API | +| `basic-auth-password` | global only | none | `--traefik-global-basic-auth-password`, `--traefik-computed-basic-auth-password` | Password for basic auth in front of the API/dashboard | +| `basic-auth-username` | global only | none | `--traefik-global-basic-auth-username`, `--traefik-computed-basic-auth-username` | Username for basic auth in front of the API/dashboard | +| `challenge-mode` | global only | `tls` | `--traefik-global-challenge-mode`, `--traefik-computed-challenge-mode` | ACME challenge method used by Traefik (`tls`, `http`, or `dns`) | +| `dashboard-enabled` | global only | `false` | `--traefik-global-dashboard-enabled`, `--traefik-computed-dashboard-enabled` | When `true`, enables the Traefik dashboard | +| `dns-provider` | global only | none | `--traefik-global-dns-provider`, `--traefik-computed-dns-provider` | Lego DNS provider name used when `challenge-mode` is `dns` | +| `dns-provider-` | global only | none | `--traefik-dns-provider-` (masked as `*******` in the default stdout report; the raw value is returned when queried via `--format json` or when this flag is requested explicitly) | Per-provider environment variables passed to the Traefik container; `` is the upstream variable name (e.g. `dns-provider-cloudflare-api-token`) | +| `http-entry-point` | global only | `http` | `--traefik-global-http-entry-point`, `--traefik-computed-http-entry-point` | Entry point name handling plaintext HTTP traffic | +| `https-entry-point` | global only | `https` | `--traefik-global-https-entry-point`, `--traefik-computed-https-entry-point` | Entry point name handling TLS-terminated HTTPS traffic | +| `image` | global only | _parsed from `plugins/traefik-vhosts/Dockerfile`_ | `--traefik-global-image`, `--traefik-computed-image` | Docker image used to run the Traefik container | +| `letsencrypt-email` | global only | none | `--traefik-global-letsencrypt-email`, `--traefik-computed-letsencrypt-email` | Contact email enabling letsencrypt; empty disables https issuance | +| `letsencrypt-server` | global only | `https://acme-v02.api.letsencrypt.org/directory` | `--traefik-global-letsencrypt-server`, `--traefik-computed-letsencrypt-server` | ACME directory used when requesting certificates | +| `log-level` | global only | `ERROR` | `--traefik-global-log-level`, `--traefik-computed-log-level` | Traefik log level | + +### Internal properties + +The following properties are not managed by `traefik:set` but are recorded internally by the plugin: + +| Property | Description | Source | +|---|---|---| +| `proxy-status` | `started`/`stopped` state of the traefik compose project | `cmd-traefik-start`/`cmd-traefik-stop` in `plugins/traefik-vhosts/command-functions` | diff --git a/docs/networking/proxy-management.md b/docs/networking/proxy-management.md index d7f5fe21d34..cda6ad6a1ab 100644 --- a/docs/networking/proxy-management.md +++ b/docs/networking/proxy-management.md @@ -1,19 +1,89 @@ # Proxy Management +> [!IMPORTANT] > New as of 0.5.0, Enhanced in 0.6.0 ``` proxy:build-config [--parallel count] [--all|] # (Re)builds config for given app +proxy:clear-config [--all|] # Clears config for given app proxy:disable [--parallel count] [--all|] # Disable proxy for app proxy:enable [--parallel count] [--all|] # Enable proxy for app proxy:report [] [] # Displays a proxy report for one or more apps -proxy:set # Set proxy type for app +proxy:set [|--global] () # Set or clear a proxy property for an app ``` -In Dokku 0.5.0, port proxying was decoupled from the `nginx-vhosts` plugin into the proxy plugin. Dokku 0.6.0 introduced the ability to map host ports to specific container ports. In the future this will allow other proxy software - such as HAProxy or Caddy - to be used in place of nginx. +In Dokku 0.5.0, port proxying was decoupled from the `nginx-vhosts` plugin into the proxy plugin. Dokku 0.6.0 introduced the ability to map host ports to specific container ports. This allows other proxy software - such as HAProxy or Caddy - to be used in place of nginx. ## Usage +### Changing the proxy + +The default proxy shipped with Dokku is `nginx`. It can be changed via the `proxy:set` command. + +```shell +dokku proxy:set node-js-app type caddy +``` + +``` +=====> Setting type to caddy +``` + +The proxy may also be set on a global basis. This is usually preferred as running multiple proxy implementations may cause port collision issues. + +```shell +dokku proxy:set --global type caddy +``` + +``` +=====> Setting type to caddy +``` + +Changing the proxy does not stop or start any given proxy implementation. Please see the documentation for your proxy implementation for details on how to perform a change. + +### Disabling and enabling the proxy + +The proxy can be disabled on a per-app basis via the `proxy:disable` command. While disabled, no proxy configuration will be generated for the app and incoming requests will not be routed through the configured proxy. + +```shell +dokku proxy:disable node-js-app +``` + +``` +-----> Disabling proxy for app +``` + +The proxy can be re-enabled with the `proxy:enable` command: + +```shell +dokku proxy:enable node-js-app +``` + +``` +-----> Enabling proxy for app +``` + +### Setting proxy ports + +When an app's domains are disabled (see [domains documentation](/docs/configuration/domains.md)), Dokku still exposes the app on a high port so that internal services can reach it at a stable port across deploys. The non-SSL and SSL ports used for this can be configured via the `proxy:set` command: + +```shell +dokku proxy:set node-js-app proxy-port 5000 +dokku proxy:set node-js-app proxy-ssl-port 5443 +``` + +Either property may also be set globally. Per-app values take precedence over the global value. + +```shell +dokku proxy:set --global proxy-port 5000 +dokku proxy:set --global proxy-ssl-port 5443 +``` + +The default value for either property may be restored by passing an empty value: + +```shell +dokku proxy:set node-js-app proxy-port +``` + ### Regenerating proxy config In certain cases, your app proxy configs may drift from the correct config for your app. You may regenerate the config at any point via the `proxy:build-config` command. This command will trigger a rebuild for the configured proxy implementation (default: nginx) for a given app. The command may fail if there are no current web listeners for your app. @@ -40,8 +110,28 @@ Finally, the number of parallel workers may be automatically set to the number o dokku proxy:build-config --all --parallel -1 ``` +### Clearing the generated proxy config + +> [!IMPORTANT] +> New as of 0.27.0 + +Generated proxy configurations can also be cleared using the `proxy:clear-config` command. + +```shell +dokku proxy:clear-config node-js-app +``` + +All apps may have their proxy config cleared by using the `--all` flag. + +```shell +dokku proxy:clear-config --all +``` + +Clearing a proxy configuration has different effects depending on the proxy plugin in use. Consul the documentation for your proxy implementation for further details. + ### Displaying proxy reports for an app +> [!IMPORTANT] > New as of 0.8.1 You can get a report about the app's proxy status using the `proxy:report` command: @@ -52,19 +142,24 @@ dokku proxy:report ``` =====> node-js-app proxy information + Proxy computed type: nginx Proxy enabled: true - Proxy type: nginx - Proxy port map: http:80:5000 https:443:5000 + Proxy global type: + Proxy type: =====> python-sample proxy information + Proxy computed type: nginx Proxy enabled: true - Proxy type: nginx - Proxy port map: http:80:5000 + Proxy global type: + Proxy type: =====> ruby-sample proxy information + Proxy computed type: nginx Proxy enabled: true - Proxy type: nginx - Proxy port map: http:80:5000 + Proxy global type: + Proxy type: ``` +The `type` and `global-type` keys hold the raw per-app and global value respectively, and are empty when nothing has been set. The `computed-type` key holds the effective value used at deploy time, falling back to the global value (where one has been set) and then to the built-in default of `nginx`. + You can run the command for a specific app also. ```shell @@ -73,35 +168,21 @@ dokku proxy:report node-js-app ``` =====> node-js-app proxy information + Proxy computed type: nginx Proxy enabled: true - Proxy type: nginx - Proxy port map: http:80:5000 https:443:5000 + Proxy global type: + Proxy type: ``` You can pass flags which will output only the value of the specific information you want. For example: ```shell -dokku proxy:report node-js-app --proxy-type +dokku proxy:report node-js-app --proxy-computed-type ``` #### Proxy Port Scheme -The proxy port scheme is as follows: - -- `SCHEME:HOST_PORT:CONTAINER_PORT` - -The scheme metadata can be used by proxy implementations in order to properly handle proxying of requests. For example, the built-in `nginx-vhosts` proxy implementation supports the `http`, `https`, `grpc` and `grpcs` schemes. -For the `grpc` and `grpcs` see [nginx blog post on grpc](https://www.nginx.com/blog/nginx-1-13-10-grpc/). - -Developers of proxy implementations are encouraged to use whatever schemes make the most sense, and ignore configurations which they do not support. For instance, a `udp` proxy implementation can safely ignore `http` and `https` port mappings. - -To change the proxy implementation in use for an application, use the `proxy:set` command: - -```shell -# no validation will be performed against -# the specified proxy implementation -dokku proxy:set node-js-app nginx -``` +See the [port scheme documentation](/docs/networking/port-management.md#port-scheme) for more information on the port mapping scheme used by dokku. ### Proxy port mapping @@ -112,3 +193,68 @@ See the [port management documentation](/docs/networking/port-management.md) for > Changed as of 0.11.0 From Dokku versions `0.5.0` until `0.11.0`, enabling or disabling an application's proxy would **also** control whether or not the application was bound to all interfaces - e.g. `0.0.0.0`. As of `0.11.0`, this is now controlled by the network plugin. Please see the [network documentation](/docs/networking/network.md#container-network-interface-binding) for more information. + +## Implementing a Proxy + +Custom plugins names _must_ have the suffix `-vhosts` or scheduler overriding via `proxy:set` may not function as expected. + +At this time, the following dokku commands are used to interact with a complete proxy implementation. + +- `domains:add`: Adds a given domain to an app. + - triggers: `post-domains-update` +- `domains:clear`: Clears out an app's associated domains. + - triggers: `post-domains-update` +- `domains:disable`: Disables domains for an app. + - triggers: `pre-disable-vhost` +- `domains:enable`: Enables domains for an app. + - triggers: `pre-enable-vhost` +- `domains:remove`: Removes a domain from an app. + - triggers: `post-domains-update` +- `domains:reset`: Reset app domains to global-configured domains. + - triggers: `post-domains-update` +- `domains:set`: Sets all domains for a given app. + - triggers: `post-domains-update` +- `proxy:build-config`: Builds - or rebuilds - external proxy configuration. + - triggers: `proxy-build-config` +- `proxy:clear-config`: Clears out external proxy configuration. + - triggers: `proxy-clear-config` +- `proxy:disable`: Disables the proxy configuration for an app. + - triggers: `proxy-disable` +- `proxy:enable`: Enables the proxy configuration for an app. + - triggers: `proxy-enable` +- `ports:add`: Adds one or more port mappings to an app + - triggers: `post-proxy-ports-update` +- `ports:clear`: Clears out all port mappings for an app. + - triggers: `post-proxy-ports-update` +- `ports:remove`: Removes one or more port mappings from an app. + - triggers: `post-proxy-ports-update` +- `ports:set`: Sets all port mappings for an app. + - triggers: `post-proxy-ports-update` + +Proxy implementations may decide to omit some functionality here, or use plugin triggers to supplement config with information from other plugins. + +Individual proxy implementations _may_ trigger app rebuilds, depending on how proxy metadata is exposed for the proxy implementation. + +Finally, proxy implementations _may_ install extra software needed for the proxy itself in whatever manner deemed fit. Proxy software can run on the host itself or within a running Docker container with either exposed ports or host networking. + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `proxy:report`. The JSON keys emitted by `proxy:report --format json` are the same names with the leading `--proxy-` stripped (e.g. `type`, `global-type`, `computed-type`). Legacy keys with the `proxy-` prefix (e.g. `proxy-type`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `disabled` | app only | `false` | `--proxy-disabled`, `--proxy-computed-disabled` (also exposed inverted as `--proxy-enabled`) | When `true`, disables proxy integration for this app (`proxy:enable`/`proxy:disable` write this) | +| `proxy-port` | app + global | none | `--proxy-proxy-port`, `--proxy-global-proxy-port`, `--proxy-computed-proxy-port` | Override port used for the HTTP listener in the generated proxy config | +| `proxy-ssl-port` | app + global | none | `--proxy-proxy-ssl-port`, `--proxy-global-proxy-ssl-port`, `--proxy-computed-proxy-ssl-port` | Override port used for the HTTPS listener in the generated proxy config | +| `type` | app + global | `nginx` | `--proxy-type`, `--proxy-global-type`, `--proxy-computed-type` | Proxy implementation handling traffic for the app (`nginx`, `caddy`, `haproxy`, `traefik`, `openresty`, or a custom plugin) | + +### Read-only flags + +The following flags surface in `proxy:report` but are not managed by `proxy:set`: + +| Flag | Description | +|---|---| +| `--proxy-enabled` | `true` when the app's `disabled` property is not `true` | diff --git a/docs/processes/entering-containers.md b/docs/processes/entering-containers.md index 5fdf908406d..1f24f7d87db 100644 --- a/docs/processes/entering-containers.md +++ b/docs/processes/entering-containers.md @@ -1,5 +1,6 @@ # Entering containers +> [!IMPORTANT] > New as of 0.4.0 ``` @@ -11,6 +12,9 @@ enter [ || --container-id ] # Connect to a The `enter` command can be used to enter a running container. The following variations of the command exist: ```shell +# enter the first container +dokku enter node-js-app + # enter the web process dokku enter node-js-app web @@ -26,7 +30,7 @@ The `container-type` argument can be one either: - If your app has a `Procfile`, the name of a process type in your `Procfile`. - If your app has no `Procfile`, the word `web`. -If the specified process type is scaled up to more than one container, then the first container will be automatically selected. this can be overriden by specifying an integer index denoting the desired container, where the first container's index is `1`. +If the specified process type is scaled up to more than one container, then the first container will be automatically selected. this can be overridden by specifying an integer index denoting the desired container, where the first container's index is `1`. Additionally, the `enter` command can be executed with no ``. If only a single `` is defined in the app's Procfile, executing `enter` will drop the terminal into the only running container. This behavior is not supported when specifying a custom command; as described below. diff --git a/docs/processes/one-off-tasks.md b/docs/processes/one-off-tasks.md index 06ba41ce1d1..f3f69911141 100644 --- a/docs/processes/one-off-tasks.md +++ b/docs/processes/one-off-tasks.md @@ -1,9 +1,11 @@ # One-off Tasks ``` -run [-e|--env KEY=VALUE] [--no-tty] # Run a command in a new container using the current app image -run:detached [-e|-env KEY=VALUE] [--no-tty] # Run a command in a new detached container using the current app image -run:list [] # List all run containers for an app +run [-e|--env KEY=VALUE] [--no-tty] [--ttl-seconds SECONDS] # Run a command in a new container using the current app image +run:detached [-e|-env KEY=VALUE] [--force-tty] [--ttl-seconds SECONDS] # Run a command in a new detached container using the current app image +run:list [--format json|stdout] [] # List all run containers for an app +run:logs [-h] [-t] [-n num] [-q] # Display recent log output for run containers +run:stop # Stops all run containers for an app or a specified run container ``` Sometimes it is necessary to run a one-off command under an app. Dokku makes it easy to run a fresh container via the `run` command. @@ -14,6 +16,7 @@ Sometimes it is necessary to run a one-off command under an app. Dokku makes it The `run` command can be used to run a one-off process for a specific command. This will start a new container and run the desired command within that container. The container image will be the same container image as was used to start the currently deployed app. +> [!IMPORTANT] > New as of 0.25.0, this container will be removed after the process exits. ```shell @@ -26,6 +29,16 @@ dokku run --env "NODE_ENV=development" --env "PATH=/custom/path" node-js-app npm One off containers are removed at the end of process execution. +In addition, one-off containers also run for a max of 24 hours (86400 seconds) by default, after which they are reaped. This can be changed by setting the `--ttl-seconds` argument: + + +```shell +# runs for 10 minutes +dokku run --ttl-seconds 600 node-js-app npm run mytask +``` + +One-off containers that exceed their runtime are reaped every 5 minutes, so the ttl-seconds is an approximation and your app may end up running for up to 5 minutes longer than expected. + #### Running Procfile commands The `run` command can also be used to run a command defined in the app `Procfile`: @@ -49,6 +62,7 @@ dokku --label=com.example.test-label=value run node-js-app ls -lah #### Disabling TTY +> [!IMPORTANT] > New as of 0.25.0 One-off containers default to interactive mode where possible. To disable this behavior, specify the `--no-tty` flag: @@ -59,17 +73,54 @@ dokku run --no-tty node-js-app ls -lah ### Running a detached container +> [!IMPORTANT] > New as of 0.25.0 -Finally, a container can be run in "detached" mode via the `run:detached` Dokku command. Running a process in detached mode will immediately return a `CONTAINER_ID`. Detached containers are run without a tty and are also removed at the end of process execution. +Finally, a container can be run in "detached" mode via the `run:detached` Dokku command. Running a process in detached mode will immediately return the container name (or pod name when using the k3s scheduler). Detached containers run without a tty by default and are also removed at the end of process execution. ```shell dokku run:detached node-js-app ls -lah # returns the ID of the new container ``` +To start a detached container with a tty, specify the `--force-tty` flag. Containers will still exit upon command termination, but can be attached to as long as they are running. + +```shell +dokku run:detached --force-tty node-js-app bash +``` + +### Displaying one-off container logs + +You can easily get logs of all one-off containers for an app using the `logs` command: + +```shell +dokku run:logs node-js-app +``` + +Logs are pulled via integration with the scheduler for the specified application via "live tailing". As such, logs from previously running deployments are usually not available. Users that desire to see logs from previous deployments for debugging purposes should persist those logs to external services. Please see Dokku's [vector integration](/docs/deployment/logs.md#vector-logging-shipping) for more information on how to persist logs across deployments to ship logs to another service or a third-party platform. + +#### Behavioral modifiers + +Dokku also supports certain command-line arguments that augment the `run:log` command's behavior. + +``` +--container NAME # the name of a specific container to show logs for +-n, --num NUM # the number of lines to display +-t, --tail # continually stream logs +-q, --quiet # display raw logs without colors, time and names +``` + +You can use these modifiers as follows: + +```shell +dokku run:logs -t --container node-js-app.run.1234 +``` + +The above command will show logs continually from the `node-js-app.run.1234` one-off run process. + ### Listing one-off containers +> [!IMPORTANT] > New as of 0.25.0 One-off containers for a given app can be listed via the `run:list` command: @@ -85,3 +136,51 @@ node-js-app.run.28689 "/exec sleep 15" 2 seconds ago ``` > The `COMMAND` displayed will be what Docker executes and may not exactly match the command specified by a `dokku run` command. + +The output can also be shown in json format: + +```shell +dokku run:list node-js-app --format json +``` + +``` +[ + { + "name": "node-js-app.run.28689", + "state": "running", + "command": "\"/exec 'sleep 15'\"", + "created_at": "2022-08-03 05:47:44 +0000 UTC" + } +] +``` + +### Stopping a one-off container + +> [!IMPORTANT] +> New as of 0.29.0 + +Run containers for an app can be stopped via the `run:stop` command. The output will be the container id. + +```shell +# start a container +# the output will be something like: node-js-app.run.2313 +dokku run node-js-app sleep 300 + +# stop the container +dokku run:stop --container node-js-app.run.2313 +``` + +``` +node-js-app.run.2313 +``` + +All containers for a given app can be stopped by specifying the app name. + +```shell +dokku run:stop node-js-app +``` + +``` +node-js-app.run.2313 +node-js-app.run.574 +``` diff --git a/docs/processes/process-management.md b/docs/processes/process-management.md index cdc2a2d9f3e..80e219291ba 100644 --- a/docs/processes/process-management.md +++ b/docs/processes/process-management.md @@ -1,23 +1,25 @@ # Process Management +> [!IMPORTANT] > New as of 0.3.14, Enhanced in 0.7.0 ``` -ps:inspect # Displays a sanitized version of docker inspect for an app -ps:rebuild [--parallel count] [--all|] # Rebuilds an app from source -ps:report [] [] # Displays a process report for one or more apps -ps:restart [--parallel count] [--all|] [] # Restart an app -ps:restore [] # Start previously running apps e.g. after reboot -ps:scale [--skip-deploy] = [=...] # Get/Set how many instances of a given process to run -ps:set # Set or clear a ps property for an app -ps:start [--parallel count] [--all|] # Start an app -ps:stop [--parallel count] [--all|] # Stop an app +ps:inspect # Displays a sanitized version of docker inspect for an app +ps:rebuild [--parallel count] [--all|] # Rebuilds an app from source +ps:report [] [] # Displays a process report for one or more apps +ps:restart [--parallel count] [--all|] [] # Restart an app +ps:restore [] # Start previously running apps e.g. after reboot +ps:scale [--skip-deploy] [--format stdout|json] [=...] # Get/Set how many instances of a given process to run +ps:set # Set or clear a ps property for an app +ps:start [--parallel count] [--all|] # Start an app +ps:stop [--parallel count] [--all|] # Stop an app ``` ## Usage ### Inspecting app containers +> [!IMPORTANT] > New as of 0.13.0 A common administrative task to perform is calling `docker inspect` on the containers that are running for an app. This can be an error-prone task to perform, and may also reveal sensitive environment variables if not done correctly. Dokku provides a wrapper around this command via the `ps:inspect` command: @@ -105,56 +107,79 @@ proctype: qty web: 1 ``` -### Scaling apps +The formation can also be retrieved as JSON by using the `--format json` flag, which is useful for programmatic consumption. Each entry maps a process type to its desired quantity: -#### Via CLI +```shell +dokku ps:scale node-js-app --format json +``` -> This functionality is disabled if the formation is managed via the `formation` key of `app.json`. +```json +[{"process_type":"web","quantity":1},{"process_type":"worker","quantity":4}] +``` -Dokku can also manage scaling itself via the `ps:scale` command. This command can be used to scale multiple process types at the same time. +When no scale has been set for the app, the JSON output is an empty array (`[]`). + +### Changing process management settings + +The `ps` plugin provides a number of settings that can be used to managed deployments on a per-app basis. The following table outlines ones not covered elsewhere: + +| Name | Description | Global Default | +|-----------------------|---------------------------------------------------|--------------------| +| `stop-timeout` | Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands *also* respect this value. If not specified, the Docker defaults for the [`docker stop` command](https://docs.docker.com/engine/reference/commandline/stop/) will be used. | `30` | + +All settings can be set via the `ps:set` command. Using `stop-timeout` as an example: ```shell -dokku ps:scale node-js-app web=1 +dokku ps:set node-js-app stop-timeout 60 ``` -Multiple process types can be scaled at once: +The default value may be set by passing an empty value for the option in question: ```shell -dokku ps:scale node-js-app web=1 worker=1 +dokku ps:set node-js-app stop-timeout ``` -If desired, the corresponding deploy will be skipped by using the `--skip-deploy` flag: +Properties can also be set globally. If not set for an app, the global value will apply. ```shell -dokku ps:scale --skip-deploy node-js-app web=1 +dokku ps:set --global stop-timeout 60 ``` -#### Manually managing process scaling +The global default value may be set by passing an empty value for the option. + +```shell +dokku ps:set --global stop-timeout +``` -> Using a `formation` key in an `app.json` file with _any_ `quantity` specified disables the ability to use `ps:scale` for scaling. +### Defining Processes -An `app.json` file can be committed to the root of the pushed app repository, and must be within the built image artifact in the image's working directory as shown below. +#### Procfile -- Buildpacks: `/app/app.json` -- Dockerfile: `WORKDIR/app.json` or `/app.json` (if no working directory specified) -- Docker Image: `WORKDIR/app.json` or `/app.json` (if no working directory specified) +> [!NOTE] +> Dokku supports the Procfile format as defined in [this document](https://github.com/dokku/procfile-util/blob/master/PROCFILE_FORMAT.md) under "Strict Mode" parsing rules. -The `formation` key should be specified as follows in the `app.json` file: +Apps can define processes to run by using a `Procfile`. A `Procfile` is a simple text file that can be used to specify multiple commands, each of which is subject to process scaling. In the case where the built image sets a default command to run - either through usage of `CMD` for Dockerfile-based builds, a default process for buildpack-based builds, or any other method for the builder in use - the `Procfile` will take precedence. + +If the file exists, it should not be empty, as doing so may result in a failed deploy. + +The syntax for declaring a `Procfile` is as follows. Note that the format is one process type per line, with no duplicate process types. ```Procfile -{ - "formation": { - "web": { - "quantity": 1 - }, - "worker": { - "quantity": 4 - } - } -} +: +``` + +If, for example, you have multiple queue workers and wish to scale them separately, the following would be a valid way to work around the requirement of not duplicating process types: + +```Procfile +worker: env QUEUE=* bundle exec rake resque:work +importantworker: env QUEUE=important bundle exec rake resque:work ``` -Removing the file will result in Dokku respecting the `ps:scale` command for setting scale values. The values set via the `app.json` file from a previous deploy will be respected. +If the app build declares an `ENTRYPOINT`, the command defined in the `Procfile` is passed as an argument to that entrypoint. This is the case for all Dockerfile-based, Docker Image, and Cloud Native Buildpack deployments. + +The `web` process type holds some significance in that it is the only process type that is automatically scaled to `1` on the initial application deploy. See the [web process scaling documentation](/docs/processes/process-management.md#the-web-process) for more details around scaling individual processes. + +See the [Procfile location documentation](/docs/processes/process-management.md#changing-the-procfile-location) for more information on where to place your `Procfile` file. #### The `web` process @@ -162,19 +187,29 @@ For initial app deploys, Dokku will default to starting a single `web` process f There are also a few other exceptions for the `web` process. -- Custom checks defined by a `CHECKS` file only apply to the `web` process type. - By default, the built-in nginx proxy implementation only proxies the `web` process (others may be handled via a custom `nginx.conf.sigil`). - - See the [nginx request proxying documentation](/docs/configuration/nginx.md#request-proxying) for more information on how nginx handles proxied requests. + - See the [nginx request proxying documentation](/docs/networking/proxies/nginx.md#request-proxying) for more information on how nginx handles proxied requests. - Only the `web` process may be bound to an external port. +#### The `release` process + +The `Procfile` also supports a special `release` command which acts in a similar way to the [Heroku Release Phase](https://devcenter.heroku.com/articles/release-phase). See the [Release deployment task documentation](/docs/advanced-usage/deployment-tasks.md#procfile-release-command) for more information on how Dokku handles this process type. + #### Changing the `Procfile` location -When deploying a monorepo, it may be desirable to specify the specific path of the `Procfile` file to use for a given app. This can be done via the `ps:set` command. If a value is specified and that file does not exist within the repository, Dokku will continue the build process as if the repository has no `Procfile`. +The `Procfile` is expected to be found in a specific directory, depending on the deploy approach: + +- The `WORKDIR` of the Docker image for deploys resulting from `git:from-image` and `git:load-image` commands. +- The root of the source code tree for all other deploys (git push, `git:from-archive`, `git:sync`). + +Sometimes it may be desirable to set a different path for a given app, e.g. when deploying from a monorepo. This can be done via the `procfile-path` property: ```shell -dokku ps:set node-js-app procfile-path Procfile2 +dokku ps:set node-js-app procfile-path .dokku/Procfile ``` +The value is the path to the desired file *relative* to the base search directory, and will never be treated as absolute paths in any context. If that file does not exist within the repository, Dokku will continue the build process as if the repository has no `Procfile`. + The default value may be set by passing an empty value for the option: ```shell @@ -193,6 +228,53 @@ The default value may be set by passing an empty value for the option. dokku ps:set --global procfile-path ``` +### Scaling apps + +#### Via CLI + +> This functionality is disabled if the formation is managed via the `formation` key of `app.json`. + +Dokku can also manage scaling itself via the `ps:scale` command. This command can be used to scale multiple process types at the same time. + +```shell +dokku ps:scale node-js-app web=1 +``` + +Multiple process types can be scaled at once: + +```shell +dokku ps:scale node-js-app web=1 worker=1 +``` + +If desired, the corresponding deploy will be skipped by using the `--skip-deploy` flag: + +```shell +dokku ps:scale --skip-deploy node-js-app web=1 +``` + +#### Manually managing process scaling + +> Using a `formation` key in an `app.json` file with _any_ `quantity` specified disables the ability to use `ps:scale` for scaling. All processes not specified in the `app.json` will have their process count set to zero. + +Users can also configure scaling within the codebase itself to manage process scaling. The `formation` key should be specified as follows in the `app.json` file: + +```json +{ + "formation": { + "web": { + "quantity": 1 + }, + "worker": { + "quantity": 4 + } + } +} +``` + +Removing the `formation` key or removing the `app.json` file from your repository will result in Dokku respecting the `ps:scale` command for setting scale values. The values set via the `app.json` file from a previous deploy will be respected. + +See the [app.json location documentation](/docs/advanced-usage/deployment-tasks.md#changing-the-appjson-location) for more information on where to place your `app.json` file. + ### Stopping apps Deployed apps can be stopped using the `ps:stop` command. This turns off all running containers for an app, and will result in a **502 Bad Gateway** response for the default nginx proxy implementation. @@ -245,8 +327,31 @@ Finally, the number of parallel workers may be automatically set to the number o dokku ps:start --all --parallel -1 ``` +### Customizing the start command + +The command used to start an app's containers can be customized via two `ps` properties, depending on which builder is in use. + +For buildpack-built apps (where the start command normally comes from the `Procfile`), set `start-cmd`: + +```shell +dokku ps:set node-js-app start-cmd "node server.js" +``` + +For Dockerfile-built apps, set `dockerfile-start-cmd`. This value is passed as arguments to `docker run` and overrides or supplements the image's `CMD` / `ENTRYPOINT`. See the [Dockerfile builder documentation](/docs/deployment/builders/dockerfiles.md#customizing-the-run-command) for context. + +```shell +dokku ps:set node-js-app dockerfile-start-cmd "--harmony server.js" +``` + +Either property can be cleared by passing an empty value: + +```shell +dokku ps:set node-js-app start-cmd +``` + ### Restart policies +> [!IMPORTANT] > New as of 0.7.0, Command Changed in 0.22.0 By default, Dokku will automatically restart containers that exit with a non-zero status up to 10 times via the [on-failure Docker restart policy](https://docs.docker.com/engine/reference/run/#restart-policies---restart). @@ -274,6 +379,20 @@ dokku ps:set node-js-app restart-policy on-failure dokku ps:set node-js-app restart-policy on-failure:20 ``` +The default policy (`on-failure:10`) may be restored by passing an empty value: + +```shell +dokku ps:set node-js-app restart-policy +``` + +A global default may also be set, and is used by any app that does not have an app-specific restart policy: + +```shell +dokku ps:set --global restart-policy always +``` + +The effective policy applied to an app's containers is resolved as the app-specific value, then the global value, then the built-in `on-failure:10` default. This computed value can be inspected via the `--ps-computed-restart-policy` report flag. + Restart policies have no bearing on server reboot, and Dokku will always attempt to restart your apps at that point unless they were manually stopped. Dokku also runs `dokku-event-listener` in the background via the system's init service. This monitors container state, performing the following actions: @@ -283,6 +402,7 @@ Dokku also runs `dokku-event-listener` in the background via the system's init s ### Displaying reports for an app +> [!IMPORTANT] > New as of 0.12.0 You can get a report about the deployed apps using the `ps:report` command: @@ -297,8 +417,10 @@ dokku ps:report Processes: 0 Ps can scale: true Ps computed procfile path: Procfile2 + Ps computed restart policy: on-failure:10 Ps global procfile path: Procfile - Ps restart policy: on-failure:10 + Ps global restart policy: + Ps restart policy: Ps procfile path: Procfile2 Restore: true Running: false @@ -307,8 +429,10 @@ dokku ps:report Processes: 0 Ps can scale: true Ps computed procfile path: Procfile + Ps computed restart policy: on-failure:10 Ps global procfile path: Procfile - Ps restart policy: on-failure:10 + Ps global restart policy: + Ps restart policy: Ps procfile path: Restore: true Running: false @@ -317,8 +441,10 @@ dokku ps:report Processes: 0 Ps can scale: true Ps computed procfile path: Procfile + Ps computed restart policy: on-failure:10 Ps global procfile path: Procfile - Ps restart policy: on-failure:10 + Ps global restart policy: + Ps restart policy: Ps procfile path: Restore: true Running: false @@ -335,7 +461,9 @@ dokku ps:report node-js-app Deployed: false Processes: 0 Ps can scale: true - Ps restart policy: on-failure:10 + Ps computed restart policy: on-failure:10 + Ps global restart policy: + Ps restart policy: Restore: true Running: false ``` @@ -353,7 +481,48 @@ When a server reboots or Docker is restarted/upgraded, Docker may or may not sta - Start all linked services. - Clear generated proxy configuration files. - Start the app if it has not been manually stopped. - - If the app containers still exist, they will be started and the generated proxy configuration files will be rebuilt. - - If any of the app containers are missing, the entire app will be rebuilt. + - If the app containers still exist, they will be started and the generated proxy configuration files will be rebuilt. + - If any of the app containers are missing, the entire app will be rebuilt. During this time, requests may route to the incorrect app if the assigned IPs correspond to those for other apps. While dokku makes all efforts to avoid this, there may be a few minutes where urls may route to the wrong app. To avoid this, either use a custom proxy plugin or wait a few minutes until the restoration process is complete. + +Per-app restore behavior can be controlled via the `restore` property. When set to `false`, `ps:restore` will skip the app on reboot: + +```shell +dokku ps:set node-js-app restore false +``` + +The default value (`true`) may be restored by passing an empty value: + +```shell +dokku ps:set node-js-app restore +``` + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `ps:report`. The JSON keys emitted by `ps:report --format json` are the same names with the leading `--ps-` stripped (e.g. `procfile-path`, `global-procfile-path`, `computed-procfile-path`). Legacy keys with the `ps-` prefix (e.g. `ps-procfile-path`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `dockerfile-start-cmd` | app only | none | `--ps-dockerfile-start-cmd`, `--ps-computed-dockerfile-start-cmd` | Override `CMD` for Dockerfile-based apps | +| `procfile-path` | app + global | `Procfile` | `--ps-procfile-path`, `--ps-global-procfile-path`, `--ps-computed-procfile-path` | Path to the app's Procfile, relative to the build root | +| `restart-policy` | app + global | `on-failure:10` | `--ps-restart-policy`, `--ps-global-restart-policy`, `--ps-computed-restart-policy` | Docker restart policy applied to deployed containers (`no`, `always`, `unless-stopped`, `on-failure[:max-retries]`) | +| `restore` | app only | `true` | `--restore` | When `true`, the app is restarted automatically by `ps:retire` after a host reboot | +| `skip-deploy` | app + global | `false` | `--ps-skip-deploy`, `--ps-global-skip-deploy`, `--ps-computed-skip-deploy` | When `true`, skips the deploy phase after a successful build | +| `start-cmd` | app only | none | `--ps-start-cmd`, `--ps-computed-start-cmd` | Override start command for buildpack apps | +| `stop-timeout-seconds` | app + global | `30` | `--ps-stop-timeout-seconds`, `--ps-global-stop-timeout-seconds`, `--ps-computed-stop-timeout-seconds` | Seconds Docker waits before SIGKILLing a container on stop | + +### Read-only flags + +The following flags surface in `ps:report` but are not managed by `ps:set` - they are derived from the running process state: + +| Flag | Description | +|---|---| +| `--ps-can-scale` | `false` when the app's procfile or builder forbids horizontal scaling | +| `--deployed` | `true` after the first successful deploy | +| `--running` | `true` while any container for the app is running | +| `--processes` | Total scaled process count across all proctypes | +| `--status-` | Container status and ID for each running proctype (one entry per Procfile process type) | diff --git a/docs/processes/scheduled-cron-tasks.md b/docs/processes/scheduled-cron-tasks.md index 716a31b5a28..12c21f507ca 100644 --- a/docs/processes/scheduled-cron-tasks.md +++ b/docs/processes/scheduled-cron-tasks.md @@ -1,10 +1,15 @@ # Scheduled Cron Tasks +> [!IMPORTANT] > New as of 0.23.0 ``` -cron:list # List scheduled cron tasks for an app -cron:report [] [] # Display report about an app +cron:list [--format json|stdout] # List scheduled cron tasks for an app +cron:report [] [] # Display report about an app +cron:resume # Resume a cron task +cron:run [--detach] # Run a cron task on the fly +cron:set [--global|] # Set or clear a cron property for an app +cron:suspend # Suspend a cron task ``` ## Usage @@ -15,9 +20,9 @@ Dokku automates scheduled `dokku run` commands via it's `app.json` cron integrat #### Specifying commands -The `app.json` file for a given app can define a special `cron` key that contains a list of commands to run on given schedules. The following is a simple example `app.json` that simply runs the command `npm run send-email` once a day: +The `app.json` file for a given app can define a special `cron` key that contains a list of commands to run on given schedules. The following is a simple example `app.json` that effectively runs the command `dokku run $APP npm run send-email` once a day: -``` +```json { "cron": [ { @@ -28,12 +33,19 @@ The `app.json` file for a given app can define a special `cron` key that contain } ``` -A cron entry takes the following properties: +A cron task takes the following properties: - `command`: A command to be run within the built app image. Specified commands can also be `Procfile` entries. +- `maintenance`: A boolean value that decides whether the cron task is in maintenance and therefore executable or not. - `schedule`: A [cron-compatible](https://en.wikipedia.org/wiki/Cron#Overview) scheduling definition upon which to run the command. Seconds are generally not supported. +- `concurrency_policy`: A string (default: `allow`), that controls whether the cron task can be run concurrently with another invocation of itself. Valid options are `allow` (allow concurrency), `forbid` (exit the new cron task if there is an existing one), `replace` (delete any existing cron task and start the new one). + + +Zero or more cron tasks can be specified per app. Cron tasks are validated after the build artifact is created but before the app is deployed, and the cron schedule is updated during the post-deploy phase. -Zero or more cron commands can be specified per app. Cron entries are validated after the build artifact is created but before the app is deployed, and the cron schedule is updated during the post-deploy phase. +Cron tasks can run for a maximum of 24 hours via the docker-local scheduler, after which they are reaped from the system. + +See the [app.json location documentation](/docs/advanced-usage/deployment-tasks.md#changing-the-appjson-location) for more information on where to place your `app.json` file. #### Task Environment @@ -42,10 +54,47 @@ When running scheduled cron tasks, there are a few items to be aware of: - Scheduled cron tasks are performed within the app environment available at runtime. If the app image does not exist, the command may fail to execute. - Schedules are performed on the hosting server's timezone, which is typically UTC. - At this time, only the `PATH` and `SHELL` environment variables are specified in the cron template. -- Each scheduled task is executed within a one-off `run` container, and thus inherit any docker-options specified for `run` containers.Resources are never shared between scheduled tasks. -- Tasks are always executed using the `--rm` flag, and are never executed in detached mode. + - A `MAILTO` value can be set via the `cron:set` command. + - A `MAILFROM` value can be set via the `cron:set` command. +- Each scheduled task is executed within a one-off `run` container, and thus inherit any docker-options specified for `run` containers. Resources are never shared between scheduled tasks. - Scheduled cron tasks are supported on a per-scheduler basis, and are currently only implemented by the `docker-local` scheduler. - Tasks for _all_ apps managed by the `docker-local` scheduler are written to a single crontab file owned by the `dokku` user. The `dokku` user's crontab should be considered reserved for this purpose. +- The `command` is tokenized and exec'd directly inside the container. Shell features such as `;`, `&&`, `|`, and `>` are _not_ interpreted. Commands that contain a bare shell operator are rejected when `app.json` is validated at deploy time, so a malformed cron command will fail the deploy rather than silently fail to run. If shell semantics are required, wrap the command explicitly, for example `"sh -c 'do-thing > /var/log/x.log'"`. + + +### Changing cron management settings + +The `cron` plugin provides a number of settings that can be used to managed deployments on a per-app basis. The following table outlines ones not covered elsewhere: + +| Name | Description | Level | Global Default | +|-----------------------|----------------------------------------------------------------|-------------|----------------| +| `mailfrom` | Sets the `MAILFROM` variable in a cron file for cron reporting | Global-only | empty string | +| `maintenance` | Whether to have cron running for the app or not. | App and Global | `false` | +| `mailto` | Sets the `MAILTO` variable in a cron file for cron reporting | Global-only | empty string | + +All settings can be set via the `cron:set` command. Using `maintenance` as an example: + +```shell +dokku cron:set node-js-app maintenance true +``` + +The default value may be set by passing an empty value for the option in question: + +```shell +dokku cron:set node-js-app maintenance +``` + +If a property can be set globally - such as `mailto`, use the `--global` flag. If not set for an app, the global value will apply if it exists. + +```shell +dokku cron:set --global maintenance true +``` + +The global default value may be set by passing an empty value for the option. + +```shell +dokku cron:set --global maintenance +``` #### Listing Cron tasks @@ -61,6 +110,63 @@ cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 @daily node index.js cGhwPT09dHJ1ZT09PSogKiAqICogKg== * * * * * true ``` +The output can also be displayed in json format: + +```shell +dokku cron:list node-js-app --format json +``` + +``` +[{"id":"cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5","app":"node-js-app","command":"node index.js","schedule":"@daily"}] +``` + +To fetch global tasks, use the `--global` flag: + +```shell +dokku cron:list --global +``` + +``` +ID Schedule Command +5cruaotm4yzzpnjlsdunblj8qyjp @daily /bin/true +``` + +#### Suspending and resuming a specific cron task + +Cron tasks can be suspended to temporarily prevent them from running, and later resumed to re-enable them. This is useful for maintenance or debugging purposes. + +To suspend a specific cron task, use the `cron:suspend` command with the app name and cron ID: + +```shell +dokku cron:suspend node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 +``` + +A suspended task will not execute according to its schedule. You can verify a task is suspended by checking the `Maintenance` column in the `cron:list` output, which will show `true (task)` for suspended tasks. + +To resume a suspended cron task, use the `cron:resume` command: + +```shell +dokku cron:resume node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 +``` + +Once resumed, the task will execute according to its schedule again. The cron ID can be retrieved from the `cron:list` output. + +#### Executing a cron task on the fly + +Cron tasks can be invoked via the `cron:run` command. This command takes an `app` argument and a `cron id` (retrievable from `cron:list` output). + +```shell +dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 +``` + +By default, the task is run in an attached container - as supported by the scheduler. To run in a background detached container, specify the `--detach` flag: + +```shell +dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 --detach +``` + +All one-off cron executions have their containers terminated after invocation. + #### Displaying reports You can get a report about the cron configuration for apps using the `cron:report` command: @@ -97,7 +203,8 @@ dokku cron:report node-js-app --cron-task-count ### Self Managed Cron -> Warning: Self-managed cron tasks should be considered advanced usage. While the instructions are available, users are highly encouraged to use the built-in scheduled cron task support unless absolutely necessary. +> [!WARNING] +> Self-managed cron tasks should be considered advanced usage. While the instructions are available, users are highly encouraged to use the built-in scheduled cron task support unless absolutely necessary. Some installations may require more fine-grained control over cron usage. The following are advanced instructions for configuring cron. @@ -106,8 +213,7 @@ Some installations may require more fine-grained control over cron usage. The fo You can always use a one-off container to run an app task: ```shell -dokku --rm run node-js-app some-command -dokku --rm-container run node-js-app some-command +dokku run node-js-app some-command ``` For tasks that should not be interrupted, run is the _preferred_ method of handling cron tasks, as the container will continue running even during a deploy or scaling event. The trade-off is that there will be an increase in memory usage if there are multiple concurrent tasks running. @@ -140,9 +246,8 @@ For tasks that will properly resume, you _should_ use the above method, as runni Regularly scheduled tasks can be a bit of a pain with Dokku. The following are general recommendations to follow to help ensure successful task runs. -- Use the `dokku` user in your cron entry. - - If you do not, the `dokku` binary will attempt to execute with `sudo`, and your cron run with fail with `sudo: no tty present and no askpass program specified`. - +- Use the `dokku` user in your cron task. + - If you do not, the `dokku` binary will attempt to execute with `sudo`, and your cron run with fail with `sudo: no tty present and no askpass program specified`. - Add a `MAILTO` environment variable to ship cron emails to yourself. - Add a `PATH` environment variable or specify the full path to binaries on the host. - Add a `SHELL` environment variable to specify Bash when running commands. @@ -183,7 +288,7 @@ SHELL=/bin/bash ### PLACE ALL CRON TASKS BELOW # removes unresponsive users from the subscriber list to decrease bounce rates -0 0 * * * dokku dokku --rm run node-js-app some-command +0 0 * * * dokku dokku run node-js-app some-command # sends out our email alerts to users 0 1 * * * dokku dokku ps:scale node-js-app cron=1 && dokku enter node-js-app cron some-other-command && dokku ps:scale node-js-app cron=0 @@ -191,3 +296,17 @@ SHELL=/bin/bash ### PLACE ALL CRON TASKS ABOVE, DO NOT REMOVE THE WHITESPACE AFTER THIS LINE ``` + +## Properties + +### Settable properties + +> [!NOTE] +> The `Report flags` column lists the CLI argument names accepted by `cron:report`. The JSON keys emitted by `cron:report --format json` are the same names with the leading `--cron-` stripped (e.g. `global-mailto`, `computed-mailto`, `maintenance`). Legacy keys with the `cron-` prefix (e.g. `cron-global-mailto`) are also emitted during the 0.38.x deprecation window and will be removed in a future major release. + +| Property | Scope | Default | Report flags | Description | +|---|---|---|---|---| +| `mailfrom` | global only | none | `--cron-global-mailfrom`, `--cron-computed-mailfrom` | `From:` address used in cron failure emails | +| `mailto` | global only | none | `--cron-global-mailto`, `--cron-computed-mailto` | Recipient address for cron failure emails; empty disables email | +| `maintenance` | app + global | `false` | `--cron-maintenance`, `--cron-global-maintenance`, `--cron-computed-maintenance` | When `true`, suspends all cron tasks for the app (or globally) | +| `maintenance.` | app only | `false` | `--cron-maintenance-` (dynamic per task) | Suspends an individual cron task by its computed ID (one row per task); written by `cron:suspend`/`cron:resume` | diff --git a/docs/template.html b/docs/template.html index 8f39da9c97b..b6bb4737298 100644 --- a/docs/template.html +++ b/docs/template.html @@ -9,26 +9,26 @@ Dokku - {{DOC_TITLE}} - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - - - + + + + + + + + + - -
-
-
-
-

- Dokku -

- -
-
-
-
- -
+
+ +
+ +
@@ -94,10 +120,7 @@

@@ -222,7 +271,19 @@

This document is for dokku's development version, which can be significantly different from previous releases. For older releases, use the version selector floating in the bottom right corner of this page.

+ +
+
+
+
+

Š Dokku. Website designed & developed by Yasoob Khalid

+
+
+
+
+ + - - - - -{{{body}}} - - \ No newline at end of file diff --git a/tests/apps/checks-root/.node-version b/tests/apps/checks-root/.node-version new file mode 100644 index 00000000000..3516580bbbc --- /dev/null +++ b/tests/apps/checks-root/.node-version @@ -0,0 +1 @@ +20.17.0 diff --git a/tests/apps/checks-root/CHECKS b/tests/apps/checks-root/CHECKS deleted file mode 100644 index 4bc292597eb..00000000000 --- a/tests/apps/checks-root/CHECKS +++ /dev/null @@ -1,2 +0,0 @@ -ATTEMPTS=2 -/ diff --git a/tests/apps/checks-root/app.json b/tests/apps/checks-root/app.json new file mode 100644 index 00000000000..0c12a6d97e4 --- /dev/null +++ b/tests/apps/checks-root/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "attempts": 2, + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/checks-root/index.js b/tests/apps/checks-root/index.js index 67ba7ab2a10..6b83f0902d9 100644 --- a/tests/apps/checks-root/index.js +++ b/tests/apps/checks-root/index.js @@ -1,5 +1,5 @@ -var express = require('express'); -var app = express(); +const express = require('express'); +const app = express(); app.get('/', function(req, res) { res.sendStatus(404); diff --git a/tests/apps/checks-root/package-lock.json b/tests/apps/checks-root/package-lock.json new file mode 100644 index 00000000000..4cc8f722eac --- /dev/null +++ b/tests/apps/checks-root/package-lock.json @@ -0,0 +1,861 @@ +{ + "name": "node-example", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-example", + "version": "0.0.1", + "dependencies": { + "express": "5.x" + }, + "engines": { + "node": "20.17.x" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz", + "integrity": "sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.0.tgz", + "integrity": "sha512-PuseHIvAnz3bjrM2rGJtSgo1zjgxapTLZ7x2pjhzWwlp4SJQgK3f3iZIQwkpEnBaKz6seKBADpM4B4ySkuYypg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/tests/apps/checks-root/package.json b/tests/apps/checks-root/package.json index d378c6327df..bb391036b4f 100644 --- a/tests/apps/checks-root/package.json +++ b/tests/apps/checks-root/package.json @@ -2,12 +2,12 @@ "name": "node-example", "version": "0.0.1", "dependencies": { - "express": "4.x" + "express": "5.x" }, "engines": { - "node": "4.2.x" + "node": "20.17.x" }, "scripts": { "start": "node index.js" } -} +} \ No newline at end of file diff --git a/tests/apps/config/.null b/tests/apps/config/.null new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/apps/config/DOKKU_SCALE b/tests/apps/config/DOKKU_SCALE deleted file mode 100644 index 1312bef2d17..00000000000 --- a/tests/apps/config/DOKKU_SCALE +++ /dev/null @@ -1,2 +0,0 @@ -# test comment -web=1 diff --git a/tests/apps/config/Procfile b/tests/apps/config/Procfile index ae7366d4107..a79eaf1c8da 100644 --- a/tests/apps/config/Procfile +++ b/tests/apps/config/Procfile @@ -1 +1 @@ -web: node web.js +web: python3 -u web.py diff --git a/tests/apps/config/app.json b/tests/apps/config/app.json new file mode 100644 index 00000000000..be4658045fb --- /dev/null +++ b/tests/apps/config/app.json @@ -0,0 +1,7 @@ +{ + "formation": { + "web": { + "quantity": 1 + } + } +} \ No newline at end of file diff --git a/tests/apps/config/check_deploy b/tests/apps/config/check_deploy index d0ad9f3aff1..358dce43366 100755 --- a/tests/apps/config/check_deploy +++ b/tests/apps/config/check_deploy @@ -11,6 +11,7 @@ test_url() { } base_url="$1" -test_url / "config-test" +test_url / "python/http.server" +test_url /conftest "config-test" test_url /hello "Hello 'world'" diff --git a/tests/apps/config/package.json b/tests/apps/config/package.json deleted file mode 100644 index fa981e758a7..00000000000 --- a/tests/apps/config/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "node-example", - "version": "0.0.1", - "dependencies": { - "express": "4.17.x" - }, - "engines": { - "node": "4.2.x" - } -} diff --git a/tests/apps/config/web.js b/tests/apps/config/web.js deleted file mode 100644 index d1a7cefa69e..00000000000 --- a/tests/apps/config/web.js +++ /dev/null @@ -1,16 +0,0 @@ -var express = require('express'); - -var app = express(); - -app.get('/', function(request, response) { - response.send(process.env.CONFTEST); -}); - -app.get('/hello', function(request, response) { - response.send(process.env.HELLO); -}); - -var port = process.env.PORT || 5000; -app.listen(port, function() { - console.log("Listening on " + port); -}); diff --git a/tests/apps/config/web.py b/tests/apps/config/web.py new file mode 100644 index 00000000000..ed15a73b07e --- /dev/null +++ b/tests/apps/config/web.py @@ -0,0 +1,37 @@ +import http.server +import json +import os +import sys + + +class GetHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header("Content-Type", "text/plain; charset=utf-8") + self.end_headers() + + if self.path == '/': + self.wfile.write("python/http.server".encode("utf-8")) + elif self.path == '/conftest': + value = os.getenv("CONFTEST", "") + self.wfile.write(value.encode("utf-8")) + elif self.path == '/hello': + value = os.getenv("HELLO", "") + self.wfile.write(value.encode("utf-8")) + else: + data = json.dumps(dict(os.environ), sort_keys=True, indent=4) + self.wfile.write(data.encode("utf-8")) + + +if __name__ == "__main__": + if os.getenv("FAIL_ON_STARTUP") == "true": + print("Failing on startup due to FAIL_ON_STARTUP=true") + sys.exit(1) + + for arg in sys.argv: + print(f"Arg: {arg}") + + port = int(os.getenv("PORT", 5000)) + server = http.server.HTTPServer(("0.0.0.0", port), GetHandler) + print("Listening on port {0}".format(port)) + server.serve_forever() diff --git a/tests/apps/dockerfile-dokku-scale/Dockerfile b/tests/apps/dockerfile-app-json-formations/Dockerfile similarity index 86% rename from tests/apps/dockerfile-dokku-scale/Dockerfile rename to tests/apps/dockerfile-app-json-formations/Dockerfile index eee64864916..aa816b081ae 100644 --- a/tests/apps/dockerfile-dokku-scale/Dockerfile +++ b/tests/apps/dockerfile-app-json-formations/Dockerfile @@ -1,4 +1,4 @@ -FROM node:4-alpine +FROM node:26-alpine RUN apk add --no-cache bash diff --git a/tests/apps/dockerfile-dokku-scale/Procfile b/tests/apps/dockerfile-app-json-formations/Procfile similarity index 100% rename from tests/apps/dockerfile-dokku-scale/Procfile rename to tests/apps/dockerfile-app-json-formations/Procfile diff --git a/tests/apps/dockerfile-app-json-formations/app.json b/tests/apps/dockerfile-app-json-formations/app.json new file mode 100644 index 00000000000..4b341198d68 --- /dev/null +++ b/tests/apps/dockerfile-app-json-formations/app.json @@ -0,0 +1,20 @@ +{ + "formation": { + "web": { + "quantity": 1 + } + }, + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "nodejs/express", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/dockerfile-dokku-scale/check_deploy b/tests/apps/dockerfile-app-json-formations/check_deploy similarity index 100% rename from tests/apps/dockerfile-dokku-scale/check_deploy rename to tests/apps/dockerfile-app-json-formations/check_deploy diff --git a/tests/apps/dockerfile-dokku-scale/package.json b/tests/apps/dockerfile-app-json-formations/package.json similarity index 86% rename from tests/apps/dockerfile-dokku-scale/package.json rename to tests/apps/dockerfile-app-json-formations/package.json index 2498df2dcd5..fbc0ffdac2f 100644 --- a/tests/apps/dockerfile-dokku-scale/package.json +++ b/tests/apps/dockerfile-app-json-formations/package.json @@ -2,7 +2,7 @@ "name": "node-example", "version": "0.0.1", "dependencies": { - "express": "4.17.x" + "express": "5.2.x" }, "engines": { "node": "4.2.x" diff --git a/tests/apps/dockerfile-dokku-scale/web.js b/tests/apps/dockerfile-app-json-formations/web.js similarity index 100% rename from tests/apps/dockerfile-dokku-scale/web.js rename to tests/apps/dockerfile-app-json-formations/web.js diff --git a/tests/apps/dockerfile-dokku-scale/worker.js b/tests/apps/dockerfile-app-json-formations/worker.js similarity index 100% rename from tests/apps/dockerfile-dokku-scale/worker.js rename to tests/apps/dockerfile-app-json-formations/worker.js diff --git a/tests/apps/dockerfile-dokku-scale/CHECKS b/tests/apps/dockerfile-dokku-scale/CHECKS deleted file mode 100644 index 06892682579..00000000000 --- a/tests/apps/dockerfile-dokku-scale/CHECKS +++ /dev/null @@ -1,5 +0,0 @@ -WAIT=2 # wait 2 seconds -TIMEOUT=5 # set timeout to 5 seconds -ATTEMPTS=2 # try 2 times - -/ nodejs/express diff --git a/tests/apps/dockerfile-dokku-scale/DOKKU_SCALE b/tests/apps/dockerfile-dokku-scale/DOKKU_SCALE deleted file mode 100644 index 3cb9829e636..00000000000 --- a/tests/apps/dockerfile-dokku-scale/DOKKU_SCALE +++ /dev/null @@ -1 +0,0 @@ -web=1 diff --git a/tests/apps/dockerfile-entrypoint/Dockerfile b/tests/apps/dockerfile-entrypoint/Dockerfile index 18d2ddaa5ac..0998cc27a38 100644 --- a/tests/apps/dockerfile-entrypoint/Dockerfile +++ b/tests/apps/dockerfile-entrypoint/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.5.1 +FROM ruby:4.0.6 RUN mkdir /app WORKDIR /app diff --git a/tests/apps/dockerfile-noexpose/CHECKS b/tests/apps/dockerfile-noexpose/CHECKS deleted file mode 100644 index 034f4c73c3e..00000000000 --- a/tests/apps/dockerfile-noexpose/CHECKS +++ /dev/null @@ -1,5 +0,0 @@ -WAIT=2 # wait 2 seconds -TIMEOUT=5 # set timeout to 5 seconds -ATTEMPTS=2 # try 2 times - -/ Hello World! diff --git a/tests/apps/dockerfile-noexpose/Dockerfile b/tests/apps/dockerfile-noexpose/Dockerfile index 61aa17519ea..d65b5a7d995 100644 --- a/tests/apps/dockerfile-noexpose/Dockerfile +++ b/tests/apps/dockerfile-noexpose/Dockerfile @@ -1,4 +1,4 @@ -FROM node:4-alpine +FROM node:26-alpine RUN apk add --no-cache bash diff --git a/tests/apps/dockerfile-noexpose/app.json b/tests/apps/dockerfile-noexpose/app.json new file mode 100644 index 00000000000..a82fa63b4e3 --- /dev/null +++ b/tests/apps/dockerfile-noexpose/app.json @@ -0,0 +1,15 @@ +{ + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "Hello World!", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/dockerfile-noexpose/package.json b/tests/apps/dockerfile-noexpose/package.json index 84913e3a00e..154c4e8a781 100644 --- a/tests/apps/dockerfile-noexpose/package.json +++ b/tests/apps/dockerfile-noexpose/package.json @@ -7,7 +7,7 @@ "start": "node index.js" }, "dependencies": { - "express": "^4.0.0" + "express": "^5.1.0" }, "engines": { "node": "4.2.x" diff --git a/tests/apps/dockerfile-procfile-bad/CHECKS b/tests/apps/dockerfile-procfile-bad/CHECKS deleted file mode 100644 index 06892682579..00000000000 --- a/tests/apps/dockerfile-procfile-bad/CHECKS +++ /dev/null @@ -1,5 +0,0 @@ -WAIT=2 # wait 2 seconds -TIMEOUT=5 # set timeout to 5 seconds -ATTEMPTS=2 # try 2 times - -/ nodejs/express diff --git a/tests/apps/dockerfile-procfile-bad/Dockerfile b/tests/apps/dockerfile-procfile-bad/Dockerfile index eee64864916..aa816b081ae 100644 --- a/tests/apps/dockerfile-procfile-bad/Dockerfile +++ b/tests/apps/dockerfile-procfile-bad/Dockerfile @@ -1,4 +1,4 @@ -FROM node:4-alpine +FROM node:26-alpine RUN apk add --no-cache bash diff --git a/tests/apps/dockerfile-procfile-bad/app.json b/tests/apps/dockerfile-procfile-bad/app.json new file mode 100644 index 00000000000..ae6ec4b08eb --- /dev/null +++ b/tests/apps/dockerfile-procfile-bad/app.json @@ -0,0 +1,15 @@ +{ + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "nodejs/express", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/dockerfile-procfile-bad/package.json b/tests/apps/dockerfile-procfile-bad/package.json index 2498df2dcd5..fbc0ffdac2f 100644 --- a/tests/apps/dockerfile-procfile-bad/package.json +++ b/tests/apps/dockerfile-procfile-bad/package.json @@ -2,7 +2,7 @@ "name": "node-example", "version": "0.0.1", "dependencies": { - "express": "4.17.x" + "express": "5.2.x" }, "engines": { "node": "4.2.x" diff --git a/tests/apps/dockerfile-procfile/CHECKS b/tests/apps/dockerfile-procfile/CHECKS deleted file mode 100644 index 06892682579..00000000000 --- a/tests/apps/dockerfile-procfile/CHECKS +++ /dev/null @@ -1,5 +0,0 @@ -WAIT=2 # wait 2 seconds -TIMEOUT=5 # set timeout to 5 seconds -ATTEMPTS=2 # try 2 times - -/ nodejs/express diff --git a/tests/apps/dockerfile-procfile/Dockerfile b/tests/apps/dockerfile-procfile/Dockerfile index eee64864916..aa816b081ae 100644 --- a/tests/apps/dockerfile-procfile/Dockerfile +++ b/tests/apps/dockerfile-procfile/Dockerfile @@ -1,4 +1,4 @@ -FROM node:4-alpine +FROM node:26-alpine RUN apk add --no-cache bash diff --git a/tests/apps/dockerfile-procfile/app-cron-procfile.json b/tests/apps/dockerfile-procfile/app-cron-procfile.json new file mode 100644 index 00000000000..22ed426fda9 --- /dev/null +++ b/tests/apps/dockerfile-procfile/app-cron-procfile.json @@ -0,0 +1,21 @@ +{ + "cron": [ + { + "command": "release", + "schedule": "5 5 5 5 5" + } + ], + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "nodejs/express", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + } +} diff --git a/tests/apps/dockerfile-procfile/app-cron.json b/tests/apps/dockerfile-procfile/app-cron.json new file mode 100644 index 00000000000..2b7361dbb49 --- /dev/null +++ b/tests/apps/dockerfile-procfile/app-cron.json @@ -0,0 +1,27 @@ +{ + "cron": [ + { + "command": "echo hello-from-cron", + "schedule": "5 5 5 5 5" + } + ], + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "nodejs/express", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, + "scripts": { + "dokku": { + "postdeploy": "touch /app/postdeploy.test", + "predeploy": "touch /app/predeploy.test" + } + } +} diff --git a/tests/apps/dockerfile-procfile/app.json b/tests/apps/dockerfile-procfile/app.json index 0dbdea3ec8b..58d88012ff5 100644 --- a/tests/apps/dockerfile-procfile/app.json +++ b/tests/apps/dockerfile-procfile/app.json @@ -1,8 +1,21 @@ { + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "nodejs/express", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, "scripts": { "dokku": { - "predeploy": "touch /app/predeploy.test", - "postdeploy": "touch /app/postdeploy.test" + "postdeploy": "touch /app/postdeploy.test", + "predeploy": "touch /app/predeploy.test" } } -} +} \ No newline at end of file diff --git a/tests/apps/dockerfile-procfile/package.json b/tests/apps/dockerfile-procfile/package.json index 2498df2dcd5..fbc0ffdac2f 100644 --- a/tests/apps/dockerfile-procfile/package.json +++ b/tests/apps/dockerfile-procfile/package.json @@ -2,7 +2,7 @@ "name": "node-example", "version": "0.0.1", "dependencies": { - "express": "4.17.x" + "express": "5.2.x" }, "engines": { "node": "4.2.x" diff --git a/tests/apps/dockerfile-release/Dockerfile b/tests/apps/dockerfile-release/Dockerfile index 1c53486a362..cd319ce9131 100644 --- a/tests/apps/dockerfile-release/Dockerfile +++ b/tests/apps/dockerfile-release/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8-buster +FROM python:3.15.0b4-bookworm RUN pip install --user pipenv ENV PATH="${PATH}:/root/.local/bin" diff --git a/tests/apps/dockerfile-release/Pipfile b/tests/apps/dockerfile-release/Pipfile index 26df7ab95b2..2aa0982fb5c 100644 --- a/tests/apps/dockerfile-release/Pipfile +++ b/tests/apps/dockerfile-release/Pipfile @@ -14,4 +14,4 @@ psycopg2-binary = "*" dj-database-url = "*" [requires] -python_version = "3.8" +python_version = "3.11" diff --git a/tests/apps/dockerfile-release/Pipfile.lock b/tests/apps/dockerfile-release/Pipfile.lock index 0532e789907..ab96fdf0b6e 100644 --- a/tests/apps/dockerfile-release/Pipfile.lock +++ b/tests/apps/dockerfile-release/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "6e4ab3d777dd8ead8bc254ab4bb72ee80c93d1c09537b30ad23beb21c8bfdba9" + "sha256": "dce0d88f476d7f4deb8a18dd3b0d1e53f5bbab7fa5a912edce990bb9e80f1b69" }, "pipfile-spec": 6, "requires": { - "python_version": "3.8" + "python_version": "3.11" }, "sources": [ { @@ -18,104 +18,162 @@ "default": { "asgiref": { "hashes": [ - "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9", - "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214" + "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", + "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133" ], - "markers": "python_version >= '3.6'", - "version": "==3.4.1" + "markers": "python_version >= '3.9'", + "version": "==3.11.1" }, "dj-database-url": { "hashes": [ - "sha256:4aeaeb1f573c74835b0686a2b46b85990571159ffc21aa57ecd4d1e1cb334163", - "sha256:851785365761ebe4994a921b433062309eb882fedd318e1b0fcecc607ed02da9" + "sha256:544e015fee3efa5127a1eb1cca465f4ace578265b3671fe61d0ed7dbafb5ec8a", + "sha256:63c20e4bbaa51690dfd4c8d189521f6bf6bc9da9fcdb23d95d2ee8ee87f9ec62" ], "index": "pypi", - "version": "==0.5.0" + "markers": "python_version >= '3.10'", + "version": "==3.1.2" }, "django": { "hashes": [ - "sha256:9f8be75646f62204320b195062b1d696ba28aa3d45ee72fb7c888ffaebc5bdb2", - "sha256:a6e0d1ff11095b7394c079ade7094c73b2dc3df4a7a373c9b58ed73b77a97feb" + "sha256:04f354bf9d807a86ad1a8392fe3808d362358a8eafc322848e0e43e59b24371d", + "sha256:59ea02020c3136fce14bef0bbece21a10a4febef5eed1c51c22ae468efa22200" ], "index": "pypi", - "version": "==3.1.13" + "markers": "python_version >= '3.10'", + "version": "==5.2.16" }, "djangorestframework": { "hashes": [ - "sha256:5cc724dc4b076463497837269107e1995b1fbc917468d1b92d188fd1af9ea789", - "sha256:a5967b68a04e0d97d10f4df228e30f5a2d82ba63b9d03e1759f84993b7bf1b53" + "sha256:a6def5f447fe78ff853bff1d47a3c59bf38f5434b031780b351b0c73a62db1a5", + "sha256:c3c74dd3e83a5a3efc37b3c18d92bd6f86a6791c7b7d4dff62bb068500e76457" ], "index": "pypi", - "version": "==3.11.2" + "markers": "python_version >= '3.10'", + "version": "==3.17.1" }, "gunicorn": { "hashes": [ - "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", - "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" + "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", + "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf" ], "index": "pypi", - "version": "==20.0.4" + "markers": "python_version >= '3.10'", + "version": "==26.0.0" + }, + "packaging": { + "hashes": [ + "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", + "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661" + ], + "markers": "python_version >= '3.8'", + "version": "==26.2" }, "psycopg2-binary": { "hashes": [ - "sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29", - "sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03", - "sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039", - "sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881", - "sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309", - "sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed", - "sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b", - "sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3", - "sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7", - "sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b", - "sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03", - "sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103", - "sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d", - "sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35", - "sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b", - "sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49", - "sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70", - "sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e", - "sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e", - "sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e", - "sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103", - "sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6", - "sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1", - "sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9", - "sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e", - "sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f", - "sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd", - "sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8", - "sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f", - "sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4", - "sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964", - "sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08" + "sha256:00814e40fa23c2b37ef0a1e3c749d89982c73a9cb5046137f0752a22d432e82f", + "sha256:049366c6d884bdcd65d66e6ca1fdbebe670b56c6c9ba46f164e6667e90881964", + "sha256:0dc9228d47c46bda253d2ecd6bb93b56a9f2d7ad33b684a1fa3622bf74ffe30c", + "sha256:1006fb62f0f0bc5ce256a832356c6262e91be43f5e4eb15b5eaf38079464caf2", + "sha256:127467c6e476dd876634f17c3d870530e73ff454ff99bff73d36e80af28e1115", + "sha256:1c8ad4c08e00f7679559eaed7aff1edfffc60c086b976f93972f686384a95e2c", + "sha256:29d4d134bd0ab46ffb04e94aa3c5fa3ef582e9026609165e2f758ff76fc3a3be", + "sha256:3471336e1acfd9c7fe507b8bad5af9317b6a89294f9eb37bd9a030bb7bebcdc6", + "sha256:36512911ebb2b60a0c3e44d0bb5048c1980aced91235d133b7874f3d1d93487c", + "sha256:398fcd4db988c7d7d3713e2b8e18939776fd3fb447052daae4f24fa39daede4c", + "sha256:3d999bd982a723113c1a45b55a7a6a90d64d0ed2278020ed625c490ff7bef96c", + "sha256:40e7b28b63aaf737cb3a1edc3a9bbc9a9f4ad3dcb7152e8c1130e4050eddcb7d", + "sha256:411e85815652d13560fbe731878daa5d92378c4995a22302071890ec3397d019", + "sha256:4413d0caef93c5cf50b96863df4c2efe8c269bf2267df353225595e7e15e8df7", + "sha256:4766ab678563054d3f1d064a4db19cc4b5f9e3a8d9018592a8285cf200c248f3", + "sha256:4dfcf8e45ebb0c663be34a3442f65e17311f3367089cd4e5e3a3e8e62c978777", + "sha256:527e6342b3e44c2f0544f6b8e927d60de7f163f5723b8f1dfa7d2a84298738cd", + "sha256:54a0dfecab1b48731f934e06139dfe11e24219fb6d0ceb32177cf0375f14c7b5", + "sha256:5a0253224780c978746cb9be55a946bcdaf40fe3519c0f622924cdabdafe2c39", + "sha256:5ac9444edc768c02a6b6a591f070b8aae28ff3a99be57560ac996001580f294c", + "sha256:5c7cb4cbf894a1d36c720d713de507952c7c58f66d30834708f03dbe5c822ccf", + "sha256:5c8ce6c61bd1b1f6b9c24ee32211599f6166af2c55abb19456090a21fd16554b", + "sha256:5cdc05117180c5fa9c40eea8ea559ce64d73824c39d928b7da9fb5f6a9392433", + "sha256:612b965daee295ae2da8f8218ce1d274645dc76ef3f1abf6a0a94fd57eff876d", + "sha256:63a3ebbd543d3d1eda088ac99164e8c5bac15293ee91f20281fd17d050aee1c4", + "sha256:66a7685d7e548f10fb4ce32fb01a7b7f4aa702134de92a292c7bd9e0d3dbd290", + "sha256:6f3b3de8a74ef8db215f22edffb19e32dc6fa41340456de7ec99efdc8a7b3ec2", + "sha256:6f9cae1f848779b5b01f417e762c40d026ea93eb0648249a604728cda991dde3", + "sha256:718e1fc18edf573b02cb8aea868de8d8d33f99ce9620206aa9144b67b0985e94", + "sha256:77b348775efd4cdab410ec6609d81ccecd1139c90265fa583a7255c8064bc03d", + "sha256:7af18183109e23502c8b2ae7f6926c0882766f35b5175a4cd737ad825e4d7a1b", + "sha256:7c729a73c7b1b84de3582f73cdd27d905121dc2c531f3d9a3c32a3011033b965", + "sha256:83946ba43979ebfdc99a3cd0ee775c89f221df026984ba19d46133d8d75d3cd9", + "sha256:840066105706cd2eb29b9a1c2329620056582a4bf3e8169dec5c447042d0869f", + "sha256:863f5d12241ebe1c76a72a04c2113b6dc905f90b9cef0e9be0efd994affd9354", + "sha256:864c261b3690e1207d14bbfe0a61e27567981b80c47a778561e49f676f7ce433", + "sha256:89d19a9f7899e8eb0656a2b3a08e0da04c720a06db6e0033eab5928aabe60fa9", + "sha256:8ffdb59fe88f99589e34354a130217aa1fd2d615612402d6edc8b3dbc7a44463", + "sha256:96937c9c5d891f772430f418a7a8b4691a90c3e6b93cf72b5bd7cad8cbca32a5", + "sha256:98062447aebc20ed20add1f547a364fd0ef8933640d5372ff1873f8deb9b61be", + "sha256:995ce929eede89db6254b50827e2b7fd61e50d11f0b116b29fffe4a2e53c4580", + "sha256:9b818ceff717f98851a64bffd4c5eb5b3059ae280276dcecc52ac658dcf006a4", + "sha256:9fe06d93e72f1c048e731a2e3e7854a5bfaa58fc736068df90b352cefe66f03f", + "sha256:a46fe069b65255df410f856d842bc235f90e22ffdf532dda625fd4213d3fd9b1", + "sha256:a7e39a65b7d2a20e4ba2e0aaad1960b61cc2888d6ab047769f8347bd3c9ad915", + "sha256:a99eaab34a9010f1a086b126de467466620a750634d114d20455f3a824aae033", + "sha256:ab29414b25dcb698bf26bf213e3348abdcd07bbd5de032a5bec15bd75b298b03", + "sha256:ace94261f43850e9e79f6c56636c5e0147978ab79eda5e5e5ebf13ae146fc8fe", + "sha256:b4a9eaa6e7f4ff91bec10aa3fb296878e75187bced5cc4bafe17dc40915e1326", + "sha256:b6937f5fe4e180aeee87de907a2fa982ded6f7f15d7218f78a083e4e1d68f2a0", + "sha256:b9a339b79d37c1b45f3235265f07cdeb0cb5ad7acd2ac7720a5920989c17c24e", + "sha256:ba3df2fc42a1cfa45b72cf096d4acb2b885937eedc61461081d53538d4a82a86", + "sha256:c41321a14dd74aceb6a9a643b9253a334521babfa763fa873e33d89cfa122fb5", + "sha256:c5ee5213445dd45312459029b8c4c0a695461eb517b753d2582315bd07995f5e", + "sha256:c6528cefc8e50fcc6f4a107e27a672058b36cc5736d665476aeb413ba88dbb06", + "sha256:cb4a1dacdd48077150dc762a9e5ddbf32c256d66cb46f80839391aa458774936", + "sha256:cfa2517c94ea3af6deb46f81e1bbd884faa63e28481eb2f889989dd8d95e5f03", + "sha256:d2fa0d7caca8635c56e373055094eeda3208d901d55dd0ff5abc1d4e47f82b56", + "sha256:d3227a3bc228c10d21011a99245edca923e4e8bf461857e869a507d9a41fe9f6", + "sha256:d6fcbba8c9fed08a73b8ac61ea79e4821e45b1e92bb466230c5e746bbf3d5256", + "sha256:e4e184b1fb6072bf05388aa41c697e1b2d01b3473f107e7ec44f186a32cfd0b8", + "sha256:ee2d84ef5eb6c04702d2e9c372ad557fb027f26a5d82804f749dfb14c7fdd2ab", + "sha256:f12ae41fcafadb39b2785e64a40f9db05d6de2ac114077457e0e7c597f3af980", + "sha256:f625abb7020e4af3432d95342daa1aa0db3fa369eed19807aa596367ba791b10", + "sha256:f921f3cd87035ef7df233383011d7a53ea1d346224752c1385f1edfd790ceb6a", + "sha256:fb1828cf3da68f99e45ebce1355d65d2d12b6a78fb5dfb16247aad6bdef5f5d2", + "sha256:ffdd7dc5463ccd61845ac37b7012d0f35a1548df9febe14f8dd549be4a0bc81e" ], "index": "pypi", - "version": "==2.8.4" + "markers": "python_version >= '3.9'", + "version": "==2.9.12" }, "pytz": { "hashes": [ - "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da", - "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" + "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588", + "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb" ], - "version": "==2021.1" + "version": "==2023.3" + }, + "setuptools": { + "hashes": [ + "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", + "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==83.0.0" }, "sqlparse": { "hashes": [ - "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", - "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" + "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", + "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e" ], - "markers": "python_version >= '3.5'", - "version": "==0.4.2" + "markers": "python_version >= '3.8'", + "version": "==0.5.5" }, "whitenoise": { "hashes": [ - "sha256:0f9137f74bd95fa54329ace88d8dc695fbe895369a632e35f7a136e003e41d73", - "sha256:62556265ec1011bd87113fb81b7516f52688887b7a010ee899ff1fd18fd22700" + "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad", + "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2" ], "index": "pypi", - "version": "==5.0.1" + "markers": "python_version >= '3.10'", + "version": "==6.12.0" } }, "develop": {} diff --git a/tests/apps/dockerfile-release/Procfile b/tests/apps/dockerfile-release/Procfile index fc5b1ca693a..cb6e6565bca 100644 --- a/tests/apps/dockerfile-release/Procfile +++ b/tests/apps/dockerfile-release/Procfile @@ -1 +1 @@ -release: pipenv run python manage.py migrate +release: pipenv run python3 manage.py migrate diff --git a/tests/apps/dockerfile/CHECKS b/tests/apps/dockerfile/CHECKS deleted file mode 100644 index 034f4c73c3e..00000000000 --- a/tests/apps/dockerfile/CHECKS +++ /dev/null @@ -1,5 +0,0 @@ -WAIT=2 # wait 2 seconds -TIMEOUT=5 # set timeout to 5 seconds -ATTEMPTS=2 # try 2 times - -/ Hello World! diff --git a/tests/apps/dockerfile/Dockerfile b/tests/apps/dockerfile/Dockerfile index eef0c10e873..478552c5387 100644 --- a/tests/apps/dockerfile/Dockerfile +++ b/tests/apps/dockerfile/Dockerfile @@ -1,4 +1,4 @@ -FROM node:4-alpine +FROM node:26-alpine RUN apk add --no-cache bash @@ -10,4 +10,7 @@ COPY . /var/www/html WORKDIR /var/www/html RUN npm install +ARG GITHUB_TOKEN=latest +RUN echo "TOKEN is: $GITHUB_TOKEN" + CMD npm start diff --git a/tests/apps/dockerfile/app.json b/tests/apps/dockerfile/app.json index ea96d901c6b..e1b65e4e0f0 100644 --- a/tests/apps/dockerfile/app.json +++ b/tests/apps/dockerfile/app.json @@ -1,20 +1,9 @@ { - "name": "Sample node.js express app", - "description": "Used in dokku's test suite", - "keywords": [ - "nodejs", - "express", - "testing" + "addons": [ + "dokku-postgres", + "dokku-redis" ], - "website": "https://dokku.com/docs/", - "repository": "https://github.com/dokku/dokku", - "logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png", - "scripts": { - "dokku": { - "predeploy": "mkdir -p /app && touch /app/predeploy.test", - "postdeploy": "mkdir -p /app && touch /app/postdeploy.test" - } - }, + "description": "Used in dokku's test suite", "env": { "SECRET_TOKEN": { "description": "A secret key for verifying the integrity of signed cookies.", @@ -22,17 +11,36 @@ }, "WEB_CONCURRENCY": { "description": "The number of processes to run.", - "generator": "echo 5" + "value": "5" } }, - "image": "gliderlabs/herokuish:latest-20", - "addons": [ - "dokku-postgres", - "dokku-redis" + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "Hello World!", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, + "image": "gliderlabs/herokuish:latest-24", + "keywords": [ + "nodejs", + "express", + "testing" ], - "buildpacks": [ - { - "url": "https://github.com/heroku/heroku-buildpack-nodejs" + "logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png", + "name": "Sample node.js express app", + "repository": "https://github.com/dokku/dokku", + "scripts": { + "dokku": { + "postdeploy": "mkdir -p /app \u0026\u0026 touch /app/postdeploy.test", + "predeploy": "mkdir -p /app \u0026\u0026 touch /app/predeploy.test" } - ] -} + }, + "website": "https://dokku.com/docs/" +} \ No newline at end of file diff --git a/tests/apps/dockerfile/package.json b/tests/apps/dockerfile/package.json index 84913e3a00e..154c4e8a781 100644 --- a/tests/apps/dockerfile/package.json +++ b/tests/apps/dockerfile/package.json @@ -7,7 +7,7 @@ "start": "node index.js" }, "dependencies": { - "express": "^4.0.0" + "express": "^5.1.0" }, "engines": { "node": "4.2.x" diff --git a/tests/apps/dockerfile/second.Dockerfile b/tests/apps/dockerfile/second.Dockerfile index d765110a502..0084d961fbf 100644 --- a/tests/apps/dockerfile/second.Dockerfile +++ b/tests/apps/dockerfile/second.Dockerfile @@ -1,4 +1,4 @@ -FROM node:4-alpine +FROM node:26-alpine RUN apk add --no-cache bash diff --git a/tests/apps/gitsubmodules/CHECKS b/tests/apps/gitsubmodules/CHECKS deleted file mode 100644 index 1be99496612..00000000000 --- a/tests/apps/gitsubmodules/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ Hello diff --git a/tests/apps/gitsubmodules/app.json b/tests/apps/gitsubmodules/app.json new file mode 100644 index 00000000000..7435327400a --- /dev/null +++ b/tests/apps/gitsubmodules/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "Hello", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/gitsubmodules/package.json b/tests/apps/gitsubmodules/package.json index 3362fbe5822..b66a507b614 100644 --- a/tests/apps/gitsubmodules/package.json +++ b/tests/apps/gitsubmodules/package.json @@ -2,7 +2,7 @@ "name": "node-example", "version": "0.0.1", "dependencies": { - "express": "~4.17.1" + "express": "~5.2.1" }, "engines": { "node": "4.2.x" diff --git a/tests/apps/go-fail-postdeploy/CHECKS b/tests/apps/go-fail-postdeploy/CHECKS deleted file mode 100644 index 52e1189c7de..00000000000 --- a/tests/apps/go-fail-postdeploy/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ go diff --git a/tests/apps/go-fail-postdeploy/Dockerfile b/tests/apps/go-fail-postdeploy/Dockerfile index ee90e692710..f71a45c652f 100644 --- a/tests/apps/go-fail-postdeploy/Dockerfile +++ b/tests/apps/go-fail-postdeploy/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.1 +FROM golang:1.26.5 EXPOSE 3000/tcp diff --git a/tests/apps/go-fail-postdeploy/app.json b/tests/apps/go-fail-postdeploy/app.json index 677a4df21a6..77352b4719d 100644 --- a/tests/apps/go-fail-postdeploy/app.json +++ b/tests/apps/go-fail-postdeploy/app.json @@ -1,17 +1,27 @@ { - "name": "Sample go app", + "buildpacks": [ + { + "url": "https://github.com/heroku/heroku-buildpack-go" + } + ], "description": "Used in dokku's test suite", - "website": "https://dokku.com/docs/", - "repository": "https://github.com/dokku/dokku", + "healthchecks": { + "web": [ + { + "content": "go", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + }, "logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png", + "name": "Sample go app", + "repository": "https://github.com/dokku/dokku", "scripts": { "dokku": { - "postdeploy": "touch /app/postdeploy.test && ls -la /app" + "postdeploy": "touch /app/postdeploy.test \u0026\u0026 ls -la /app" } }, - "buildpacks": [ - { - "url": "https://github.com/heroku/heroku-buildpack-go" - } - ] -} + "website": "https://dokku.com/docs/" +} \ No newline at end of file diff --git a/tests/apps/go-fail-predeploy/CHECKS b/tests/apps/go-fail-predeploy/CHECKS deleted file mode 100644 index 52e1189c7de..00000000000 --- a/tests/apps/go-fail-predeploy/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ go diff --git a/tests/apps/go-fail-predeploy/Dockerfile b/tests/apps/go-fail-predeploy/Dockerfile index ee90e692710..f71a45c652f 100644 --- a/tests/apps/go-fail-predeploy/Dockerfile +++ b/tests/apps/go-fail-predeploy/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.1 +FROM golang:1.26.5 EXPOSE 3000/tcp diff --git a/tests/apps/go-fail-predeploy/app.json b/tests/apps/go-fail-predeploy/app.json index 94ea57a6db1..a5f72478fff 100644 --- a/tests/apps/go-fail-predeploy/app.json +++ b/tests/apps/go-fail-predeploy/app.json @@ -1,17 +1,27 @@ { - "name": "Sample go app", + "buildpacks": [ + { + "url": "https://github.com/heroku/heroku-buildpack-go" + } + ], "description": "Used in dokku's test suite", - "website": "https://dokku.com/docs/", - "repository": "https://github.com/dokku/dokku", + "healthchecks": { + "web": [ + { + "content": "go", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + }, "logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png", + "name": "Sample go app", + "repository": "https://github.com/dokku/dokku", "scripts": { "dokku": { - "predeploy": "touch /app/predeploy.test && ls -la /app" + "predeploy": "touch /app/predeploy.test \u0026\u0026 ls -la /app" } }, - "buildpacks": [ - { - "url": "https://github.com/heroku/heroku-buildpack-go" - } - ] -} + "website": "https://dokku.com/docs/" +} \ No newline at end of file diff --git a/tests/apps/go/CHECKS b/tests/apps/go/CHECKS deleted file mode 100644 index 52e1189c7de..00000000000 --- a/tests/apps/go/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ go diff --git a/tests/apps/go/app.json b/tests/apps/go/app.json new file mode 100644 index 00000000000..36e31ada78f --- /dev/null +++ b/tests/apps/go/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "go", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/gogrpc/Dockerfile b/tests/apps/gogrpc/Dockerfile index b1adfc38f83..85d1d7ee580 100644 --- a/tests/apps/gogrpc/Dockerfile +++ b/tests/apps/gogrpc/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.1 +FROM golang:1.26.5 RUN mkdir /app WORKDIR /app diff --git a/tests/apps/gogrpc/go.mod b/tests/apps/gogrpc/go.mod index 3a41967b011..2894518aefb 100644 --- a/tests/apps/gogrpc/go.mod +++ b/tests/apps/gogrpc/go.mod @@ -1,18 +1,17 @@ module github.com/dokku/dokko/tests/apps/gorpc -go 1.17 +go 1.26.2 require ( - github.com/golang/protobuf v1.5.2 - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 - golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect - golang.org/x/text v0.3.2 // indirect - google.golang.org/grpc v1.29.1 + golang.org/x/net v0.55.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + google.golang.org/grpc v1.83.0 ) require ( - google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect - google.golang.org/protobuf v1.26.0 // indirect + google.golang.org/grpc/examples v0.0.0-20240118175532-987df1309236 + google.golang.org/protobuf v1.36.11 ) -replace google.golang.org/grpc/examples/helloworld/helloworld => ./helloworld +require google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect diff --git a/tests/apps/gogrpc/go.sum b/tests/apps/gogrpc/go.sum index 8ff7da52bdd..594be5c85d7 100644 --- a/tests/apps/gogrpc/go.sum +++ b/tests/apps/gogrpc/go.sum @@ -1,64 +1,40 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ= +google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/grpc/examples v0.0.0-20240118175532-987df1309236 h1:wTUkwrodi4IuWD3kYkMUwCU8BhwFaqDywrXmsthCvQA= +google.golang.org/grpc/examples v0.0.0-20240118175532-987df1309236/go.mod h1:j5uROIAAgi3YmtiETMt1LW0d/lHqQ7wwrIY4uGRXLQ4= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= diff --git a/tests/apps/gogrpc/greeter_client/main.go b/tests/apps/gogrpc/greeter_client/main.go index 7274c10882c..48b3504aefb 100644 --- a/tests/apps/gogrpc/greeter_client/main.go +++ b/tests/apps/gogrpc/greeter_client/main.go @@ -23,18 +23,22 @@ import ( "context" "crypto/tls" "flag" - "fmt" "log" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" pb "google.golang.org/grpc/examples/helloworld/helloworld" ) +const ( + defaultName = "world" +) + var ( - address = flag.String("address", "", "address to call") - name = flag.String("name", "world", "the name") + addr = flag.String("addr", "localhost:50051", "the address to connect to") + name = flag.String("name", defaultName, "Name to greet") selfSignedTLS = flag.Bool("tls", false, "use self signed tls") ) @@ -46,9 +50,10 @@ func main() { creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}) opts = append(opts, grpc.WithTransportCredentials(creds)) } else { - opts = append(opts, grpc.WithInsecure()) + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) } - conn, err := grpc.Dial(*address, opts...) + conn, err := grpc.Dial(*addr, opts...) + if err != nil { log.Fatalf("did not connect: %v", err) } @@ -62,5 +67,5 @@ func main() { if err != nil { log.Fatalf("could not greet: %v", err) } - fmt.Printf("Greeting: %s", r.Message) + log.Printf("Greeting: %s", r.GetMessage()) } diff --git a/tests/apps/gogrpc/greeter_server/main.go b/tests/apps/gogrpc/greeter_server/main.go index d05eba7471e..7a62a9b9ff2 100644 --- a/tests/apps/gogrpc/greeter_server/main.go +++ b/tests/apps/gogrpc/greeter_server/main.go @@ -16,13 +16,13 @@ * */ -//go:generate protoc -I ../helloworld --go_out=plugins=grpc:../helloworld ../helloworld/helloworld.proto - // Package main implements a server for Greeter service. package main import ( "context" + "flag" + "fmt" "log" "net" @@ -30,28 +30,30 @@ import ( pb "google.golang.org/grpc/examples/helloworld/helloworld" ) -const ( - port = ":50051" +var ( + port = flag.Int("port", 50051, "The server port") ) // server is used to implement helloworld.GreeterServer. -type server struct{ +type server struct { pb.UnimplementedGreeterServer } // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { - log.Printf("Received: %v", in.Name) - return &pb.HelloReply{Message: "Hello " + in.Name}, nil + log.Printf("Received: %v", in.GetName()) + return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil } func main() { - lis, err := net.Listen("tcp", port) + flag.Parse() + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) + log.Printf("server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } diff --git a/tests/apps/gogrpc/helloworld/helloworld.pb.go b/tests/apps/gogrpc/helloworld/helloworld.pb.go index 11383d7402d..92299693d1c 100644 --- a/tests/apps/gogrpc/helloworld/helloworld.pb.go +++ b/tests/apps/gogrpc/helloworld/helloworld.pb.go @@ -1,198 +1,235 @@ +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // Code generated by protoc-gen-go. DO NOT EDIT. -// source: helloworld.proto +// versions: +// protoc-gen-go v1.31.0 +// protoc v4.22.0 +// source: examples/helloworld/helloworld/helloworld.proto package helloworld -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) // The request message containing the user's name. type HelloRequest struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *HelloRequest) Reset() { *m = HelloRequest{} } -func (m *HelloRequest) String() string { return proto.CompactTextString(m) } -func (*HelloRequest) ProtoMessage() {} -func (*HelloRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_helloworld_71e208cbdc16936b, []int{0} -} -func (m *HelloRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_HelloRequest.Unmarshal(m, b) -} -func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic) + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` } -func (dst *HelloRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_HelloRequest.Merge(dst, src) + +func (x *HelloRequest) Reset() { + *x = HelloRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_examples_helloworld_helloworld_helloworld_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } } -func (m *HelloRequest) XXX_Size() int { - return xxx_messageInfo_HelloRequest.Size(m) + +func (x *HelloRequest) String() string { + return protoimpl.X.MessageStringOf(x) } -func (m *HelloRequest) XXX_DiscardUnknown() { - xxx_messageInfo_HelloRequest.DiscardUnknown(m) + +func (*HelloRequest) ProtoMessage() {} + +func (x *HelloRequest) ProtoReflect() protoreflect.Message { + mi := &file_examples_helloworld_helloworld_helloworld_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var xxx_messageInfo_HelloRequest proto.InternalMessageInfo +// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. +func (*HelloRequest) Descriptor() ([]byte, []int) { + return file_examples_helloworld_helloworld_helloworld_proto_rawDescGZIP(), []int{0} +} -func (m *HelloRequest) GetName() string { - if m != nil { - return m.Name +func (x *HelloRequest) GetName() string { + if x != nil { + return x.Name } return "" } // The response message containing the greetings type HelloReply struct { - Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -func (m *HelloReply) Reset() { *m = HelloReply{} } -func (m *HelloReply) String() string { return proto.CompactTextString(m) } -func (*HelloReply) ProtoMessage() {} -func (*HelloReply) Descriptor() ([]byte, []int) { - return fileDescriptor_helloworld_71e208cbdc16936b, []int{1} + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` } -func (m *HelloReply) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_HelloReply.Unmarshal(m, b) -} -func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic) -} -func (dst *HelloReply) XXX_Merge(src proto.Message) { - xxx_messageInfo_HelloReply.Merge(dst, src) -} -func (m *HelloReply) XXX_Size() int { - return xxx_messageInfo_HelloReply.Size(m) -} -func (m *HelloReply) XXX_DiscardUnknown() { - xxx_messageInfo_HelloReply.DiscardUnknown(m) -} - -var xxx_messageInfo_HelloReply proto.InternalMessageInfo -func (m *HelloReply) GetMessage() string { - if m != nil { - return m.Message +func (x *HelloReply) Reset() { + *x = HelloReply{} + if protoimpl.UnsafeEnabled { + mi := &file_examples_helloworld_helloworld_helloworld_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } - return "" } -func init() { - proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") - proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") +func (x *HelloReply) String() string { + return protoimpl.X.MessageStringOf(x) } -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// GreeterClient is the client API for Greeter service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type GreeterClient interface { - // Sends a greeting - SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) -} +func (*HelloReply) ProtoMessage() {} -type greeterClient struct { - cc *grpc.ClientConn +func (x *HelloReply) ProtoReflect() protoreflect.Message { + mi := &file_examples_helloworld_helloworld_helloworld_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { - return &greeterClient{cc} +// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. +func (*HelloReply) Descriptor() ([]byte, []int) { + return file_examples_helloworld_helloworld_helloworld_proto_rawDescGZIP(), []int{1} } -func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { - out := new(HelloReply) - err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) - if err != nil { - return nil, err +func (x *HelloReply) GetMessage() string { + if x != nil { + return x.Message } - return out, nil + return "" } -// GreeterServer is the server API for Greeter service. -type GreeterServer interface { - // Sends a greeting - SayHello(context.Context, *HelloRequest) (*HelloReply, error) +var File_examples_helloworld_helloworld_helloworld_proto protoreflect.FileDescriptor + +var file_examples_helloworld_helloworld_helloworld_proto_rawDesc = []byte{ + 0x0a, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, + 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x22, 0x22, 0x0a, + 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x49, 0x0a, 0x07, 0x47, 0x72, 0x65, + 0x65, 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, + 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, + 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x22, 0x00, 0x42, 0x67, 0x0a, 0x1b, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, + 0x72, 0x6c, 0x64, 0x42, 0x0f, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x35, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, + 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, + 0x6c, 0x64, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_examples_helloworld_helloworld_helloworld_proto_rawDescOnce sync.Once + file_examples_helloworld_helloworld_helloworld_proto_rawDescData = file_examples_helloworld_helloworld_helloworld_proto_rawDesc +) + +func file_examples_helloworld_helloworld_helloworld_proto_rawDescGZIP() []byte { + file_examples_helloworld_helloworld_helloworld_proto_rawDescOnce.Do(func() { + file_examples_helloworld_helloworld_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_examples_helloworld_helloworld_helloworld_proto_rawDescData) + }) + return file_examples_helloworld_helloworld_helloworld_proto_rawDescData } -func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { - s.RegisterService(&_Greeter_serviceDesc, srv) +var file_examples_helloworld_helloworld_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_examples_helloworld_helloworld_helloworld_proto_goTypes = []interface{}{ + (*HelloRequest)(nil), // 0: helloworld.HelloRequest + (*HelloReply)(nil), // 1: helloworld.HelloReply +} +var file_examples_helloworld_helloworld_helloworld_proto_depIdxs = []int32{ + 0, // 0: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest + 1, // 1: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name } -func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(HelloRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(GreeterServer).SayHello(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/helloworld.Greeter/SayHello", +func init() { file_examples_helloworld_helloworld_helloworld_proto_init() } +func file_examples_helloworld_helloworld_helloworld_proto_init() { + if File_examples_helloworld_helloworld_helloworld_proto != nil { + return } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + if !protoimpl.UnsafeEnabled { + file_examples_helloworld_helloworld_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HelloRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_examples_helloworld_helloworld_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HelloReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } - return interceptor(ctx, in, info, handler) -} - -var _Greeter_serviceDesc = grpc.ServiceDesc{ - ServiceName: "helloworld.Greeter", - HandlerType: (*GreeterServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "SayHello", - Handler: _Greeter_SayHello_Handler, + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_examples_helloworld_helloworld_helloworld_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "helloworld.proto", -} - -func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_helloworld_71e208cbdc16936b) } - -var fileDescriptor_helloworld_71e208cbdc16936b = []byte{ - // 175 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, - 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88, - 0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, - 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, - 0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, - 0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a, - 0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64, - 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0x19, 0x70, 0x49, 0x67, 0xe6, - 0xeb, 0xa5, 0x17, 0x15, 0x24, 0xeb, 0xa5, 0x56, 0x24, 0xe6, 0x16, 0xe4, 0xa4, 0x16, 0x23, 0xa9, - 0x75, 0xe2, 0x07, 0x2b, 0x0e, 0x07, 0xb1, 0x03, 0x40, 0x5e, 0x0a, 0x60, 0x4c, 0x62, 0x03, 0xfb, - 0xcd, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00, + GoTypes: file_examples_helloworld_helloworld_helloworld_proto_goTypes, + DependencyIndexes: file_examples_helloworld_helloworld_helloworld_proto_depIdxs, + MessageInfos: file_examples_helloworld_helloworld_helloworld_proto_msgTypes, + }.Build() + File_examples_helloworld_helloworld_helloworld_proto = out.File + file_examples_helloworld_helloworld_helloworld_proto_rawDesc = nil + file_examples_helloworld_helloworld_helloworld_proto_goTypes = nil + file_examples_helloworld_helloworld_helloworld_proto_depIdxs = nil } diff --git a/tests/apps/gogrpc/helloworld/helloworld.proto b/tests/apps/gogrpc/helloworld/helloworld.proto index d79a6a0d1f5..692ef9deda0 100644 --- a/tests/apps/gogrpc/helloworld/helloworld.proto +++ b/tests/apps/gogrpc/helloworld/helloworld.proto @@ -14,6 +14,7 @@ syntax = "proto3"; +option go_package = "google.golang.org/grpc/examples/helloworld/helloworld"; option java_multiple_files = true; option java_package = "io.grpc.examples.helloworld"; option java_outer_classname = "HelloWorldProto"; diff --git a/tests/apps/gogrpc/helloworld/helloworld_grpc.pb.go b/tests/apps/gogrpc/helloworld/helloworld_grpc.pb.go new file mode 100644 index 00000000000..55e4f31df3c --- /dev/null +++ b/tests/apps/gogrpc/helloworld/helloworld_grpc.pb.go @@ -0,0 +1,125 @@ +// Copyright 2015 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.22.0 +// source: examples/helloworld/helloworld/helloworld.proto + +package helloworld + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + Greeter_SayHello_FullMethodName = "/helloworld.Greeter/SayHello" +) + +// GreeterClient is the client API for Greeter service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type GreeterClient interface { + // Sends a greeting + SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) +} + +type greeterClient struct { + cc grpc.ClientConnInterface +} + +func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { + return &greeterClient{cc} +} + +func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { + out := new(HelloReply) + err := c.cc.Invoke(ctx, Greeter_SayHello_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// GreeterServer is the server API for Greeter service. +// All implementations must embed UnimplementedGreeterServer +// for forward compatibility +type GreeterServer interface { + // Sends a greeting + SayHello(context.Context, *HelloRequest) (*HelloReply, error) + mustEmbedUnimplementedGreeterServer() +} + +// UnimplementedGreeterServer must be embedded to have forward compatible implementations. +type UnimplementedGreeterServer struct { +} + +func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") +} +func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {} + +// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to GreeterServer will +// result in compilation errors. +type UnsafeGreeterServer interface { + mustEmbedUnimplementedGreeterServer() +} + +func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) { + s.RegisterService(&Greeter_ServiceDesc, srv) +} + +func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HelloRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GreeterServer).SayHello(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Greeter_SayHello_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Greeter_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "helloworld.Greeter", + HandlerType: (*GreeterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SayHello", + Handler: _Greeter_SayHello_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "examples/helloworld/helloworld/helloworld.proto", +} diff --git a/tests/apps/java/CHECKS b/tests/apps/java/CHECKS deleted file mode 100644 index e15e203ed36..00000000000 --- a/tests/apps/java/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ Hello from Java diff --git a/tests/apps/java/app.json b/tests/apps/java/app.json new file mode 100644 index 00000000000..125e3c7b2df --- /dev/null +++ b/tests/apps/java/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "Hello from Java", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/java/pom.xml b/tests/apps/java/pom.xml index 82c3fc9dc04..fe42a956c2a 100644 --- a/tests/apps/java/pom.xml +++ b/tests/apps/java/pom.xml @@ -10,7 +10,7 @@ org.eclipse.jetty jetty-servlet - 11.0.7 + 11.0.26 javax.servlet @@ -23,7 +23,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.2.0 + 3.11.0 copy-dependencies diff --git a/tests/apps/lambda-python/function.py b/tests/apps/lambda-python/function.py new file mode 100644 index 00000000000..2c5a5dcf590 --- /dev/null +++ b/tests/apps/lambda-python/function.py @@ -0,0 +1,6 @@ +import requests + + +def handler(event, context): + print("Hello World!") + return "Hello World!" diff --git a/tests/apps/lambda-python/requirements.txt b/tests/apps/lambda-python/requirements.txt new file mode 100644 index 00000000000..480d0c4bbf8 --- /dev/null +++ b/tests/apps/lambda-python/requirements.txt @@ -0,0 +1 @@ +requests==2.32.4 \ No newline at end of file diff --git a/tests/apps/multi/.bowerrc b/tests/apps/multi/.bowerrc deleted file mode 100644 index a12e6286d70..00000000000 --- a/tests/apps/multi/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "static/components" -} diff --git a/tests/apps/multi/.gitignore b/tests/apps/multi/.gitignore index a337af83402..b1cfddeac8f 100644 --- a/tests/apps/multi/.gitignore +++ b/tests/apps/multi/.gitignore @@ -1,6 +1,6 @@ node_modules -static/components static/.tmp .sass-cache .bundle .DS_Store +templates/content.html diff --git a/tests/apps/multi/CHECKS b/tests/apps/multi/CHECKS deleted file mode 100644 index 9d3f9b3af0f..00000000000 --- a/tests/apps/multi/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ Heroku Multi Buildpack on Dokku diff --git a/tests/apps/multi/Gemfile b/tests/apps/multi/Gemfile index 623b9e6f25f..1aaf9d8ff46 100644 --- a/tests/apps/multi/Gemfile +++ b/tests/apps/multi/Gemfile @@ -1,6 +1,5 @@ source "https://rubygems.org" -ruby '2.6.8' +ruby '4.0.2' -gem 'compass', ">= 1.0.1" -gem 'sass-globbing' +gem 'kramdown', '~> 2.5' diff --git a/tests/apps/multi/Gemfile.lock b/tests/apps/multi/Gemfile.lock index c851f95aa64..4ccb8045430 100644 --- a/tests/apps/multi/Gemfile.lock +++ b/tests/apps/multi/Gemfile.lock @@ -1,31 +1,22 @@ GEM remote: https://rubygems.org/ specs: - chunky_png (1.3.11) - compass (1.0.3) - chunky_png (~> 1.2) - compass-core (~> 1.0.2) - compass-import-once (~> 1.0.5) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9) - sass (>= 3.3.13, < 3.5) - compass-core (1.0.3) - multi_json (~> 1.0) - sass (>= 3.3.0, < 3.5) - compass-import-once (1.0.5) - sass (>= 3.2, < 3.5) - ffi (1.11.1) - multi_json (1.14.1) - rb-fsevent (0.10.3) - rb-inotify (0.10.0) - ffi (~> 1.0) - sass (3.4.25) - sass-globbing (1.1.5) - sass (>= 3.1) + kramdown (2.5.2) + rexml (>= 3.4.4) + rexml (3.4.4) PLATFORMS ruby DEPENDENCIES - compass (>= 1.0.1) - sass-globbing + kramdown (~> 2.5) + +CHECKSUMS + kramdown (2.5.2) sha256=1ba542204c66b6f9111ff00dcc26075b95b220b07f2905d8261740c82f7f02fa + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + +RUBY VERSION + ruby 4.0.2 + +BUNDLED WITH + 4.0.6 diff --git a/tests/apps/multi/Gruntfile.js b/tests/apps/multi/Gruntfile.js deleted file mode 100644 index 5423761fb8d..00000000000 --- a/tests/apps/multi/Gruntfile.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -module.exports = function (grunt) { - - require('load-grunt-tasks')(grunt); - - grunt.initConfig({ - - compass: { - options: { - sassDir: 'static/styles', - cssDir: 'static/.tmp/styles', - generatedImagesDir: 'static/.tmp/images/generated', - imagesDir: 'static/images', - javascriptsDir: 'static/scripts', - fontsDir: 'static/styles/fonts', - importPath: 'static/components', - httpImagesPath: '/images', - httpGeneratedImagesPath: '/images/generated', - httpFontsPath: '/styles/fonts', - relativeAssets: false, - assetCacheBuster: false, - bundleExec: true, - raw: 'Sass::Script::Number.precision = 10\n' - }, - dist: { - options: { - // generatedImagesDir: '<%= yeoman.dist %>/static/images/generated' - } - }, - server: { - options: { - debugInfo: false - } - } - } - }); - - grunt.registerTask('heroku', [ - 'compass:server' - ]); - -}; diff --git a/tests/apps/multi/README.md b/tests/apps/multi/README.md index cba503104f1..8e71ff876a4 100644 --- a/tests/apps/multi/README.md +++ b/tests/apps/multi/README.md @@ -1,3 +1,3 @@ -This is a sample application to test heroku buildpack multi for integrating with herokuish: https://github.com/gliderlabs/herokuish +This is a sample application to test heroku buildpack multi for integrating with [herokuish](https://github.com/gliderlabs/herokuish) -I am choosing to use the python, nodejs, and ruby buildpacks to create a very simple flask app that performs a bower install and a simple grunt task using compass. +It uses the python, nodejs, and ruby buildpacks to create a simple flask app. The ruby buildpack provides kramdown for Markdown-to-HTML conversion. The node buildpack manages Bootstrap via npm and compiles SCSS with dart-sass. The python buildpack runs the Flask application. diff --git a/tests/apps/multi/app.json b/tests/apps/multi/app.json new file mode 100644 index 00000000000..3b2c20efab6 --- /dev/null +++ b/tests/apps/multi/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "Heroku Multi Buildpack on Dokku", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/multi/app.py b/tests/apps/multi/app.py index 07f27b63c30..15cadb96a4c 100644 --- a/tests/apps/multi/app.py +++ b/tests/apps/multi/app.py @@ -1,9 +1,6 @@ import os from flask import Flask, render_template -try: - from werkzeug.wsgi import SharedDataMiddleware -except ImportError: - from werkzeug.middleware.shared_data import SharedDataMiddleware +from werkzeug.middleware.shared_data import SharedDataMiddleware app = Flask(__name__) @@ -11,8 +8,8 @@ def hello_world(): return render_template('index.html') -app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { '/': os.path.join(os.path.dirname(__file__), 'static') }) -app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { '/': os.path.join(os.path.dirname(__file__), 'static/.tmp') }) +app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {'/': os.path.join(os.path.dirname(__file__), 'static')}) +app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {'/': os.path.join(os.path.dirname(__file__), 'static/.tmp')}) if __name__ == '__main__': app.run(host='0.0.0.0', debug=True) diff --git a/tests/apps/multi/bower.json b/tests/apps/multi/bower.json deleted file mode 100644 index 773511264b6..00000000000 --- a/tests/apps/multi/bower.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "test-buildpack-multi", - "version": "0.0.0", - "authors": [ - "Al Johri " - ], - "license": "MIT", - "private": true, - "directory": "static/components", - "ignore": [ - "**/.*", - "node_modules", - "statc/components", - "test", - "tests" - ], - "dependencies": { - "bootstrap-sass": "twbs/bootstrap-sass#~3.1.1" - } -} diff --git a/tests/apps/multi/content.md b/tests/apps/multi/content.md new file mode 100644 index 00000000000..d9705035356 --- /dev/null +++ b/tests/apps/multi/content.md @@ -0,0 +1,3 @@ +# Hello, world! + +This is a sample website using the Heroku Multi Buildpack on Dokku. It uses the Python, Ruby, and Node buildpacks. The Node buildpack compiles SCSS stylesheets with dart-sass and manages frontend dependencies. The Ruby buildpack converts Markdown content to HTML with kramdown. And last but not least, the Python buildpack runs the Flask application. diff --git a/tests/apps/multi/generate_content.rb b/tests/apps/multi/generate_content.rb new file mode 100644 index 00000000000..e4999df8744 --- /dev/null +++ b/tests/apps/multi/generate_content.rb @@ -0,0 +1,10 @@ +require 'kramdown' + +input_path = File.join(__dir__, 'content.md') +output_path = File.join(__dir__, 'templates', 'content.html') + +markdown = File.read(input_path) +html = Kramdown::Document.new(markdown).to_html + +File.write(output_path, html) +puts "Generated #{output_path} from #{input_path}" diff --git a/tests/apps/multi/package-lock.json b/tests/apps/multi/package-lock.json index 726fe5bfb86..4304ae76ef5 100644 --- a/tests/apps/multi/package-lock.json +++ b/tests/apps/multi/package-lock.json @@ -1,790 +1,464 @@ { "name": "test-buildpack-multi", "version": "0.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "argparse": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz", - "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", - "requires": { - "underscore": "~1.7.0", - "underscore.string": "~2.4.0" - }, + "packages": { + "": { + "name": "test-buildpack-multi", + "version": "0.0.0", + "hasInstallScript": true, + "license": "ISC", "dependencies": { - "underscore.string": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz", - "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=" - } - } - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "async": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz", - "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE=" - }, - "bin-version": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-1.0.4.tgz", - "integrity": "sha1-nrSY7m/Xb3q5p8FgQ2+JV5Q1144=", - "requires": { - "find-versions": "^1.0.0" - } - }, - "bin-version-check": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-2.1.0.tgz", - "integrity": "sha1-5OXfKQuQaffRETJAMe/BP90RpbA=", - "requires": { - "bin-version": "^1.0.0", - "minimist": "^1.1.0", - "semver": "^4.0.3", - "semver-truncate": "^1.0.0" - } - }, - "bower": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bower/-/bower-1.8.12.tgz", - "integrity": "sha512-u1xy9SrwwoPlgjuHNjhV+YUPVdqyBj2ALBxuzeIUKXaPI2i2xypGgxqXkuHcITGdi5yBj5JuXgyMvgiWiS1S3Q==" - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - } - }, - "coffee-script": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.3.3.tgz", - "integrity": "sha1-FQ1rTLUiiUNp7+1qIQHCC8f0pPQ=" - }, - "colors": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz", - "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=" - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "requires": { - "array-find-index": "^1.0.1" - } - }, - "dargs": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-2.1.0.tgz", - "integrity": "sha1-RsJ/+rH/sTeO8hJZchNxn+YCvJM=" - }, - "dateformat": { - "version": "1.0.2-1.2.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz", - "integrity": "sha1-sCIMAt6YYXQztyhRz0fePfLNvuk=" - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "esprima": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz", - "integrity": "sha1-n1V+CPw7TSbs6d00+Pv0drYlha0=" - }, - "eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=" - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=" - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "find-versions": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-1.2.1.tgz", - "integrity": "sha1-y96fEuOFdaCvG+G5osXV/Y8Ya2I=", - "requires": { - "array-uniq": "^1.0.0", - "get-stdin": "^4.0.1", - "meow": "^3.5.0", - "semver-regex": "^1.0.0" - } - }, - "findup-sync": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.1.3.tgz", - "integrity": "sha1-fz56l7gjksZTvwZYm9hRkOk8NoM=", - "requires": { - "glob": "~3.2.9", - "lodash": "~2.4.1" + "bootstrap": "^5.3.8", + "sass": "^1.102.0" }, + "engines": { + "node": "24.x.x", + "npm": "11.x.x" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, "dependencies": { - "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", - "requires": { - "inherits": "2", - "minimatch": "0.3" - } - }, - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - }, - "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" - }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=" - }, - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" }, - "dependencies": { - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=" - } - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=" - }, - "grunt": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-0.4.5.tgz", - "integrity": "sha1-VpN81RlDJK3/bSB2MYMqnWuk5/A=", - "requires": { - "async": "~0.1.22", - "coffee-script": "~1.3.3", - "colors": "~0.6.2", - "dateformat": "1.0.2-1.2.3", - "eventemitter2": "~0.4.13", - "exit": "~0.1.1", - "findup-sync": "~0.1.2", - "getobject": "~0.1.0", - "glob": "~3.1.21", - "grunt-legacy-log": "~0.1.0", - "grunt-legacy-util": "~0.2.0", - "hooker": "~0.2.3", - "iconv-lite": "~0.2.11", - "js-yaml": "~2.0.5", - "lodash": "~0.9.2", - "minimatch": "~0.2.12", - "nopt": "~1.0.10", - "rimraf": "~2.2.8", - "underscore.string": "~2.2.1", - "which": "~1.0.5" - } - }, - "grunt-cli": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-0.1.13.tgz", - "integrity": "sha1-6evEBHYx9QEtkidww5N4EzytEPQ=", - "requires": { - "findup-sync": "~0.1.0", - "nopt": "~1.0.10", - "resolve": "~0.3.1" - } - }, - "grunt-contrib-compass": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grunt-contrib-compass/-/grunt-contrib-compass-1.0.4.tgz", - "integrity": "sha1-SNdlY3M1qN7W4QYGM9c3bFFOHPs=", - "requires": { - "async": "^0.9.0", - "bin-version-check": "^2.0.0", - "dargs": "^2.0.3", - "onetime": "^1.0.0", - "tmp": "0.0.24" + "engines": { + "node": ">= 10.0.0" }, - "dependencies": { - "async": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" - }, - "tmp": { - "version": "0.0.24", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.24.tgz", - "integrity": "sha1-1qXhmNFKmDXMby18PZ4wJCjIzxI=" - } - } - }, - "grunt-legacy-log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-0.1.3.tgz", - "integrity": "sha1-7ClCboAwIa9ZAp+H0vnNczWgVTE=", - "requires": { - "colors": "~0.6.2", - "grunt-legacy-log-utils": "~0.1.1", - "hooker": "~0.2.3", - "lodash": "~2.4.1", - "underscore.string": "~2.3.3" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" - }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=" - } - } - }, - "grunt-legacy-log-utils": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-0.1.1.tgz", - "integrity": "sha1-wHBrndkGThFvNvI/5OawSGcsD34=", - "requires": { - "colors": "~0.6.2", - "lodash": "~2.4.1", - "underscore.string": "~2.3.3" + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" }, - "dependencies": { - "lodash": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", - "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=" + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" } }, - "grunt-legacy-util": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-0.2.0.tgz", - "integrity": "sha1-kzJIhNv343qf98Am3/RR2UqeVUs=", - "requires": { - "async": "~0.1.22", - "exit": "~0.1.1", - "getobject": "~0.1.0", - "hooker": "~0.2.3", - "lodash": "~0.9.2", - "underscore.string": "~2.2.1", - "which": "~1.0.5" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=" - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "iconv-lite": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz", - "integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=" - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "requires": { - "repeating": "^2.0.0" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-core-module": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", - "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", - "requires": { - "has": "^1.0.3" - } - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "js-yaml": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-2.0.5.tgz", - "integrity": "sha1-olrmUJmZ6X3yeMZxnaEb0Gh3Q6g=", - "requires": { - "argparse": "~ 0.1.11", - "esprima": "~ 1.0.2" - } - }, - "load-grunt-tasks": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-1.0.0.tgz", - "integrity": "sha1-NKxnBIWb1q4by9fjwh96D8bqx9c=", - "requires": { - "findup-sync": "^0.1.2", - "multimatch": "^1.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", "dependencies": { - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - } - } - }, - "lodash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-0.9.2.tgz", - "integrity": "sha1-jzSZxSRdNG1oLlsNO0B2fgnxqSw=" - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - } - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "multimatch": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-1.0.1.tgz", - "integrity": "sha1-GFR8/iWNAf0zJDWVONv68QRqfI8=", - "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "minimatch": "^1.0.0" + "readdirp": "^5.0.0" }, - "dependencies": { - "minimatch": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz", - "integrity": "sha1-4N0hILSeG3JM6NcUxSCCKpQ4V20=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "engines": { + "node": ">= 20.19.0" }, - "dependencies": { - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - } - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" } }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "license": "MIT" }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "optional": true, "dependencies": { - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" - } - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "requires": { - "is-finite": "^1.0.0" - } - }, - "resolve": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-0.3.1.tgz", - "integrity": "sha1-NMY0R8ZkxwWY0cmxJvxDsqJDEKQ=" - }, - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=" - }, - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" - }, - "semver-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", - "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=" - }, - "semver-truncate": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-1.1.2.tgz", - "integrity": "sha1-V/Qd5pcHpicJp+AQS6IRcQnqR+g=", - "requires": { - "semver": "^5.3.0" + "is-extglob": "^2.1.1" }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "spdx-license-ids": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", - "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==" - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "requires": { - "get-stdin": "^4.0.1" + "node_modules/sass": { + "version": "1.102.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz", + "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==", + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, - "tmp": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.23.tgz", - "integrity": "sha1-3odKpel0qF8KMs39vXRmPLO9nHQ=" - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" - }, - "underscore": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", - "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=" - }, - "underscore.string": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.2.1.tgz", - "integrity": "sha1-18D6KvXVoaZ/QlPa7pgTLnM/Dxk=" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" } - }, - "which": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", - "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=" } } } diff --git a/tests/apps/multi/package.json b/tests/apps/multi/package.json index 9b01c3ee6de..895f992249a 100644 --- a/tests/apps/multi/package.json +++ b/tests/apps/multi/package.json @@ -1,22 +1,18 @@ { "author": "Al Johri", "dependencies": { - "bower": "^1.3.1", - "grunt": "~0.4.5", - "grunt-cli": "~0.1.13", - "grunt-contrib-compass": "~1.0.1", - "load-grunt-tasks": "~1.0.0", - "tmp": "0.0.23" + "bootstrap": "^5.3.8", + "sass": "^1.102.0" }, "description": "Test application for heroku multi buildpack.", "engines": { - "node": "6.x.x", - "npm": "6.x.x" + "node": "24.x.x", + "npm": "11.x.x" }, "license": "ISC", "name": "test-buildpack-multi", "scripts": { - "postinstall": "echo $PATH && ./node_modules/bower/bin/bower install --allow-root && ./node_modules/grunt-cli/bin/grunt --verbose --debug heroku" + "postinstall": "bundle exec ruby generate_content.rb && mkdir -p static/.tmp/styles && npx sass static/styles/main.scss static/.tmp/styles/main.css --load-path=node_modules" }, "version": "0.0.0" } diff --git a/tests/apps/multi/requirements.txt b/tests/apps/multi/requirements.txt index d6aaf4861ff..2153f5fe4c0 100644 --- a/tests/apps/multi/requirements.txt +++ b/tests/apps/multi/requirements.txt @@ -1,2 +1,2 @@ -Flask==2.0.2 -gunicorn==20.1.0 +Flask==3.1.3 +gunicorn==26.0.0 diff --git a/tests/apps/multi/static/styles/main.scss b/tests/apps/multi/static/styles/main.scss index 20b7ae98109..53c8e5041db 100644 --- a/tests/apps/multi/static/styles/main.scss +++ b/tests/apps/multi/static/styles/main.scss @@ -1,6 +1,3 @@ -// override scss variables within bootsrap by declaring them -// before importing bootstrap +$primary: #428bca; -$brand-primary: #428bca !default; - -@import "bootstrap-sass/vendor/assets/stylesheets/bootstrap"; +@import "bootstrap/scss/bootstrap"; diff --git a/tests/apps/multi/templates/index.html b/tests/apps/multi/templates/index.html index 4e7806e85d9..0fc25bdd1a2 100644 --- a/tests/apps/multi/templates/index.html +++ b/tests/apps/multi/templates/index.html @@ -6,29 +6,23 @@ Sample Webpage - + - -
-
-
-
-

- Hello, world! -

-

- This is a sample website using the Heroku Multi Buildpack on Dokku. It uses the python, ruby, and node buildpacks. The node buildpack is for using bower and grunt. The ruby buildpack is for running compass. And last but not last, the python buildpack is to run the flask application. -

-

- Learn more -

-
-
-
-
+
+
+
+
+ {% include 'content.html' %} +

+ Learn more +

+
+
+
+
diff --git a/tests/apps/nodejs-express-noappjson/CHECKS b/tests/apps/nodejs-express-noappjson/CHECKS deleted file mode 100644 index 67d14794a6d..00000000000 --- a/tests/apps/nodejs-express-noappjson/CHECKS +++ /dev/null @@ -1,5 +0,0 @@ -WAIT=2 # wait 2 seconds -TIMEOUT=5 # set timeout to 5 seconds -ATTEMPTS=2 # try 2 times - -/ nodejs/express \ No newline at end of file diff --git a/tests/apps/nodejs-express-noappjson/app.json b/tests/apps/nodejs-express-noappjson/app.json new file mode 100644 index 00000000000..ae6ec4b08eb --- /dev/null +++ b/tests/apps/nodejs-express-noappjson/app.json @@ -0,0 +1,15 @@ +{ + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "nodejs/express", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/nodejs-express-noappjson/package.json b/tests/apps/nodejs-express-noappjson/package.json index fa981e758a7..213e958c210 100644 --- a/tests/apps/nodejs-express-noappjson/package.json +++ b/tests/apps/nodejs-express-noappjson/package.json @@ -2,9 +2,9 @@ "name": "node-example", "version": "0.0.1", "dependencies": { - "express": "4.17.x" + "express": "5.2.x" }, "engines": { - "node": "4.2.x" + "node": "18.x.x" } } diff --git a/tests/apps/nodejs-express-noprocfile/CHECKS b/tests/apps/nodejs-express-noprocfile/CHECKS deleted file mode 100644 index f1d88bab389..00000000000 --- a/tests/apps/nodejs-express-noprocfile/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ nodejs/express diff --git a/tests/apps/nodejs-express-noprocfile/app.json b/tests/apps/nodejs-express-noprocfile/app.json new file mode 100644 index 00000000000..b6ce34d811c --- /dev/null +++ b/tests/apps/nodejs-express-noprocfile/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "nodejs/express", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/nodejs-express-noprocfile/package.json b/tests/apps/nodejs-express-noprocfile/package.json index c55dba4c8b2..216976a245f 100644 --- a/tests/apps/nodejs-express-noprocfile/package.json +++ b/tests/apps/nodejs-express-noprocfile/package.json @@ -2,7 +2,7 @@ "name": "node-example", "version": "0.0.1", "dependencies": { - "express": "4.17.x" + "express": "5.2.x" }, "engines": { "node": "4.2.x" diff --git a/tests/apps/nodejs-express/CHECKS b/tests/apps/nodejs-express/CHECKS deleted file mode 100644 index 67d14794a6d..00000000000 --- a/tests/apps/nodejs-express/CHECKS +++ /dev/null @@ -1,5 +0,0 @@ -WAIT=2 # wait 2 seconds -TIMEOUT=5 # set timeout to 5 seconds -ATTEMPTS=2 # try 2 times - -/ nodejs/express \ No newline at end of file diff --git a/tests/apps/nodejs-express/app.json b/tests/apps/nodejs-express/app.json index aad3fd5905e..702f97410c9 100644 --- a/tests/apps/nodejs-express/app.json +++ b/tests/apps/nodejs-express/app.json @@ -1,20 +1,14 @@ { - "name": "Sample node.js express app", - "description": "Used in dokku's test suite", - "keywords": [ - "nodejs", - "express", - "testing" + "addons": [ + "dokku-postgres", + "dokku-redis" ], - "website": "https://dokku.com/docs/", - "repository": "https://github.com/dokku/dokku", - "logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png", - "scripts": { - "dokku": { - "predeploy": "touch /app/predeploy.test", - "postdeploy": "touch /app/postdeploy.test" + "buildpacks": [ + { + "url": "https://github.com/heroku/heroku-buildpack-nodejs" } - }, + ], + "description": "Used in dokku's test suite", "env": { "SECRET_TOKEN": { "description": "A secret key for verifying the integrity of signed cookies.", @@ -22,17 +16,36 @@ }, "WEB_CONCURRENCY": { "description": "The number of processes to run.", - "generator": "echo 5" + "value": "5" } }, - "image": "gliderlabs/herokuish:latest-20", - "addons": [ - "dokku-postgres", - "dokku-redis" + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "nodejs/express", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, + "image": "gliderlabs/herokuish:latest-24", + "keywords": [ + "nodejs", + "express", + "testing" ], - "buildpacks": [ - { - "url": "https://github.com/heroku/heroku-buildpack-nodejs" + "logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png", + "name": "Sample node.js express app", + "repository": "https://github.com/dokku/dokku", + "scripts": { + "dokku": { + "postdeploy": "touch /app/postdeploy.test", + "predeploy": "touch /app/predeploy.test" } - ] -} + }, + "website": "https://dokku.com/docs/" +} \ No newline at end of file diff --git a/tests/apps/nodejs-express/package.json b/tests/apps/nodejs-express/package.json index fa981e758a7..213e958c210 100644 --- a/tests/apps/nodejs-express/package.json +++ b/tests/apps/nodejs-express/package.json @@ -2,9 +2,9 @@ "name": "node-example", "version": "0.0.1", "dependencies": { - "express": "4.17.x" + "express": "5.2.x" }, "engines": { - "node": "4.2.x" + "node": "18.x.x" } } diff --git a/tests/apps/nodejs-worker/DOKKU_SCALE b/tests/apps/nodejs-worker/DOKKU_SCALE deleted file mode 100644 index 4a98d07fdab..00000000000 --- a/tests/apps/nodejs-worker/DOKKU_SCALE +++ /dev/null @@ -1,2 +0,0 @@ -web=0 -worker=1 diff --git a/tests/apps/nodejs-worker/app.json b/tests/apps/nodejs-worker/app.json new file mode 100644 index 00000000000..44dce8ca9b7 --- /dev/null +++ b/tests/apps/nodejs-worker/app.json @@ -0,0 +1,10 @@ +{ + "formation": { + "web": { + "quantity": 0 + }, + "worker": { + "quantity": 1 + } + } +} diff --git a/tests/apps/php/CHECKS b/tests/apps/php/CHECKS deleted file mode 100644 index d75864ad643..00000000000 --- a/tests/apps/php/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/

php

diff --git a/tests/apps/php/app.json b/tests/apps/php/app.json new file mode 100644 index 00000000000..a1fce7a1c6b --- /dev/null +++ b/tests/apps/php/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "\u003chtml\u003e\u003ch3\u003ephp\u003c/h3\u003e\u003c/html\u003e", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/php/composer.json b/tests/apps/php/composer.json index 858680a92c6..779925b5b88 100644 --- a/tests/apps/php/composer.json +++ b/tests/apps/php/composer.json @@ -2,9 +2,9 @@ "description": "a test php app for dokku", "name": "test-php-app/0.1", "require": { - "monolog/monolog": "~1.7", - "php": ">=5.5.9", - "silex/silex": "~2.2" + "monolog/monolog": "^3.0", + "php": ">=8.2", + "slim/slim": "^4.0" }, "require-dev": { "heroku/heroku-buildpack-php": "*" diff --git a/tests/apps/php/composer.lock b/tests/apps/php/composer.lock index 69ed432d463..3adc71ca847 100644 --- a/tests/apps/php/composer.lock +++ b/tests/apps/php/composer.lock @@ -4,55 +4,72 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "25734948ac43783241048a01843ef00c", + "content-hash": "bd07a729449c8e7bfadef3c601be8f69", "packages": [ { "name": "monolog/monolog", - "version": "1.26.1", + "version": "3.10.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5" + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c6b00f05152ae2c9b04a448f99c7590beb6042f5", - "reference": "c6b00f05152ae2c9b04a448f99c7590beb6042f5", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/log-implementation": "3.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "aws/aws-sdk-php": "^3.0", "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpstan/phpstan": "^0.12.59", - "phpunit/phpunit": "~4.5", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Monolog\\": "src/Monolog" @@ -66,16 +83,20 @@ { "name": "Jordi Boggiano", "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" + "homepage": "https://seld.be" } ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "homepage": "https://github.com/Seldaek/monolog", "keywords": [ "log", "logging", "psr-3" ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, "funding": [ { "url": "https://github.com/Seldaek", @@ -86,128 +107,79 @@ "type": "tidelift" } ], - "time": "2021-05-28T08:32:12+00:00" + "time": "2026-01-02T08:56:05+00:00" }, { - "name": "paragonie/random_compat", - "version": "v2.0.18", + "name": "nikic/fast-route", + "version": "v1.3.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db" + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", - "reference": "0a58ef6e3146256cc3dc7cc393927bcc7d1b72db", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", "shasum": "" }, "require": { - "php": ">=5.2.0" + "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "phpunit/phpunit": "^4.8.35|~5.7" }, "type": "library", "autoload": { "files": [ - "lib/random.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "time": "2019-01-03T20:59:08+00:00" - }, - { - "name": "pimple/pimple", - "version": "v3.2.3", - "source": { - "type": "git", - "url": "https://github.com/silexphp/Pimple.git", - "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", - "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/container": "^1.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "^3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2.x-dev" - } - }, - "autoload": { - "psr-0": { - "Pimple": "src/" + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nikita Popov", + "email": "nikic@php.net" } ], - "description": "Pimple, a simple Dependency Injection Container", - "homepage": "http://pimple.sensiolabs.org", + "description": "Fast request router for PHP", "keywords": [ - "container", - "dependency injection" + "router", + "routing" ], - "time": "2018-01-21T07:42:36+00:00" + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" }, { "name": "psr/container", - "version": "1.0.0", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -222,7 +194,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", @@ -234,34 +206,39 @@ "container-interop", "psr" ], - "time": "2017-02-14T16:28:37+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "psr/log", - "version": "1.1.4", + "name": "psr/http-factory", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -274,81 +251,48 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ - "log", + "factory", + "http", + "message", "psr", - "psr-3" + "psr-17", + "psr-7", + "request", + "response" ], - "time": "2021-05-03T11:20:27+00:00" + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" }, { - "name": "silex/silex", - "version": "v2.2.4", + "name": "psr/http-message", + "version": "2.0", "source": { "type": "git", - "url": "https://github.com/silexphp/Silex.git", - "reference": "d2531e5b8099c429b752ad2154e85999c3689057" + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Silex/zipball/d2531e5b8099c429b752ad2154e85999c3689057", - "reference": "d2531e5b8099c429b752ad2154e85999c3689057", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.5.9", - "pimple/pimple": "~3.0", - "symfony/event-dispatcher": "~2.8|^3.0", - "symfony/http-foundation": "~2.8|^3.0", - "symfony/http-kernel": "~2.8|^3.0", - "symfony/routing": "~2.8|^3.0" - }, - "conflict": { - "phpunit/phpunit": "<4.8.35 || >= 5.0, <5.4.3" - }, - "replace": { - "silex/api": "self.version", - "silex/providers": "self.version" - }, - "require-dev": { - "doctrine/dbal": "~2.2", - "monolog/monolog": "^1.4.1", - "swiftmailer/swiftmailer": "~5", - "symfony/asset": "~2.8|^3.0", - "symfony/browser-kit": "~2.8|^3.0", - "symfony/config": "~2.8|^3.0", - "symfony/css-selector": "~2.8|^3.0", - "symfony/debug": "~2.8|^3.0", - "symfony/doctrine-bridge": "~2.8|^3.0", - "symfony/dom-crawler": "~2.8|^3.0", - "symfony/expression-language": "~2.8|^3.0", - "symfony/finder": "~2.8|^3.0", - "symfony/form": "~2.8|^3.0", - "symfony/intl": "~2.8|^3.0", - "symfony/monolog-bridge": "~2.8|^3.0", - "symfony/options-resolver": "~2.8|^3.0", - "symfony/phpunit-bridge": "^3.2", - "symfony/process": "~2.8|^3.0", - "symfony/security": "~2.8|^3.0", - "symfony/serializer": "~2.8|^3.0", - "symfony/translation": "~2.8|^3.0", - "symfony/twig-bridge": "~2.8|^3.0", - "symfony/validator": "~2.8|^3.0", - "symfony/var-dumper": "~2.8|^3.0", - "symfony/web-link": "^3.3", - "twig/twig": "~1.28|~2.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Silex\\": "src/Silex" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -357,318 +301,110 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "The PHP micro-framework based on the Symfony Components", - "homepage": "http://silex.sensiolabs.org", + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ - "microframework" + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" ], - "abandoned": "symfony/flex", - "time": "2018-03-16T23:34:20+00:00" - }, - { - "name": "symfony/debug", - "version": "v3.4.35", - "source": { - "type": "git", - "url": "https://github.com/symfony/debug.git", - "reference": "f72e33fdb1170b326e72c3157f0cd456351dd086" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/f72e33fdb1170b326e72c3157f0cd456351dd086", - "reference": "f72e33fdb1170b326e72c3157f0cd456351dd086", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0" - }, - "conflict": { - "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" - }, - "require-dev": { - "symfony/http-kernel": "~2.8|~3.0|~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Debug\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Debug Component", - "homepage": "https://symfony.com", - "time": "2019-10-24T15:33:53+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v3.4.35", + "name": "psr/http-server-handler", + "version": "1.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "f9031c22ec127d4a2450760f81a8677fe8a10177" + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/f9031c22ec127d4a2450760f81a8677fe8a10177", - "reference": "f9031c22ec127d4a2450760f81a8677fe8a10177", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" - }, - "conflict": { - "symfony/dependency-injection": "<3.3" - }, - "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2019-10-24T15:33:53+00:00" - }, - { - "name": "symfony/http-foundation", - "version": "v3.4.35", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "9e4b3ac8fa3348b4811674d23de32d201de225ce" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9e4b3ac8fa3348b4811674d23de32d201de225ce", - "reference": "9e4b3ac8fa3348b4811674d23de32d201de225ce", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php70": "~1.6" - }, - "require-dev": { - "symfony/expression-language": "~2.8|~3.0|~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" + "Psr\\Http\\Server\\": "src/" } }, - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony HttpFoundation Component", - "homepage": "https://symfony.com", - "time": "2019-11-11T12:53:10+00:00" - }, - { - "name": "symfony/http-kernel", - "version": "v3.4.35", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "e1764b3de00ec5636dd03d02fd44bcb1147d70d9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/e1764b3de00ec5636dd03d02fd44bcb1147d70d9", - "reference": "e1764b3de00ec5636dd03d02fd44bcb1147d70d9", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8", - "psr/log": "~1.0", - "symfony/debug": "^3.3.3|~4.0", - "symfony/event-dispatcher": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~3.4.12|~4.0.12|^4.1.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php56": "~1.8" - }, - "conflict": { - "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.4.10|<4.0.10,>=4", - "symfony/var-dumper": "<3.3", - "twig/twig": "<1.34|<2.4,>=2" - }, - "provide": { - "psr/log-implementation": "1.0" - }, - "require-dev": { - "psr/cache": "~1.0", - "symfony/browser-kit": "~2.8|~3.0|~4.0", - "symfony/class-loader": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0|~4.0", - "symfony/console": "~2.8|~3.0|~4.0", - "symfony/css-selector": "~2.8|~3.0|~4.0", - "symfony/dependency-injection": "^3.4.10|^4.0.10", - "symfony/dom-crawler": "~2.8|~3.0|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/finder": "~2.8|~3.0|~4.0", - "symfony/process": "~2.8|~3.0|~4.0", - "symfony/routing": "~3.4|~4.0", - "symfony/stopwatch": "~2.8|~3.0|~4.0", - "symfony/templating": "~2.8|~3.0|~4.0", - "symfony/translation": "~2.8|~3.0|~4.0", - "symfony/var-dumper": "~3.3|~4.0" - }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/finder": "", - "symfony/var-dumper": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpKernel\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" ], - "description": "Symfony HttpKernel Component", - "homepage": "https://symfony.com", - "time": "2019-11-13T08:44:50+00:00" + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "name": "psr/http-server-middleware", + "version": "1.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Psr\\Http\\Server\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -676,57 +412,54 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", + "description": "Common interface for HTTP server-side middleware", "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" ], - "time": "2019-08-06T08:03:45+00:00" + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.12.0", + "name": "psr/log", + "version": "3.0.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Psr\\Log\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -734,56 +467,76 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "log", + "psr", + "psr-3" ], - "time": "2019-08-06T08:03:45+00:00" + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" }, { - "name": "symfony/polyfill-php56", - "version": "v1.12.0", + "name": "slim/slim", + "version": "4.15.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "0e3b212e96a51338639d8ce175c046d7729c3403" + "url": "https://github.com/slimphp/Slim.git", + "reference": "e12cb05ca2a14e8f459d019e87a31dc915b80470" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403", - "reference": "0e3b212e96a51338639d8ce175c046d7729c3403", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/e12cb05ca2a14e8f459d019e87a31dc915b80470", + "reference": "e12cb05ca2a14e8f459d019e87a31dc915b80470", "shasum": "" }, "require": { - "php": ">=5.3.3", - "symfony/polyfill-util": "~1.0" + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.12-dev" - } + "require-dev": { + "adriansuter/php-autoload-override": "^1.4 || ^2", + "ext-simplexml": "*", + "guzzlehttp/psr7": "^2.6", + "httpsoft/http-message": "^1.1", + "httpsoft/http-server-request": "^1.1", + "laminas/laminas-diactoros": "^2.17 || ^3", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.1", + "phpstan/phpstan": "^1 || ^2", + "phpunit/phpunit": "^9.6 || ^10 || ^11 || ^12", + "slim/http": "^1.3", + "slim/psr7": "^1.6", + "squizlabs/php_codesniffer": "^3.10", + "vimeo/psalm": "^5 || ^6" }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information." + }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php56\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Slim\\": "Slim" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -791,225 +544,75 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2019-08-06T08:03:45+00:00" - }, - { - "name": "symfony/polyfill-php70", - "version": "v1.12.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "54b4c428a0054e254223797d2713c31e08610831" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", - "reference": "54b4c428a0054e254223797d2713c31e08610831", - "shasum": "" - }, - "require": { - "paragonie/random_compat": "~1.0|~2.0|~9.99", - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.12-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "https://silentworks.co.uk" }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "https://akrabat.com" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2019-08-06T08:03:45+00:00" - }, - { - "name": "symfony/polyfill-util", - "version": "v1.12.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "4317de1386717b4c22caed7725350a8887ab205c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c", - "reference": "4317de1386717b4c22caed7725350a8887ab205c", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.12-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Util\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "https://www.lgse.com" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" } ], - "description": "Symfony utilities for portability of PHP codes", - "homepage": "https://symfony.com", + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://www.slimframework.com", "keywords": [ - "compat", - "compatibility", - "polyfill", - "shim" - ], - "time": "2019-08-06T08:03:45+00:00" - }, - { - "name": "symfony/routing", - "version": "v3.4.32", - "source": { - "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "8b0faa681c4ee14701e76a7056fef15ac5384163" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8b0faa681c4ee14701e76a7056fef15ac5384163", - "reference": "8b0faa681c4ee14701e76a7056fef15ac5384163", - "shasum": "" - }, - "require": { - "php": "^5.5.9|>=7.0.8" - }, - "conflict": { - "symfony/config": "<3.3.1", - "symfony/dependency-injection": "<3.3", - "symfony/yaml": "<3.4" + "api", + "framework", + "micro", + "router" + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" }, - "require-dev": { - "doctrine/annotations": "~1.0", - "psr/log": "~1.0", - "symfony/config": "^3.3.1|~4.0", - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/http-foundation": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation loader", - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Routing\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ + "funding": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "url": "https://opencollective.com/slimphp", + "type": "open_collective" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" } ], - "description": "Symfony Routing Component", - "homepage": "https://symfony.com", - "keywords": [ - "router", - "routing", - "uri", - "url" - ], - "time": "2019-08-26T07:50:50+00:00" + "time": "2026-05-22T08:00:12+00:00" } ], "packages-dev": [ { "name": "heroku/heroku-buildpack-php", - "version": "v205", + "version": "v293", "source": { "type": "git", "url": "https://github.com/heroku/heroku-buildpack-php.git", - "reference": "8937f4b1ab1697b12c06369ed3b0eada2525ee94" + "reference": "76afc414bf3f106b7e50f67a0631f28765288f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/heroku/heroku-buildpack-php/zipball/8937f4b1ab1697b12c06369ed3b0eada2525ee94", - "reference": "8937f4b1ab1697b12c06369ed3b0eada2525ee94", + "url": "https://api.github.com/repos/heroku/heroku-buildpack-php/zipball/76afc414bf3f106b7e50f67a0631f28765288f12", + "reference": "76afc414bf3f106b7e50f67a0631f28765288f12", "shasum": "" }, "bin": [ @@ -1037,17 +640,21 @@ "nginx", "php" ], - "time": "2022-01-07T12:51:32+00:00" + "support": { + "issues": "https://github.com/heroku/heroku-buildpack-php/issues", + "source": "https://github.com/heroku/heroku-buildpack-php/tree/v293" + }, + "time": "2026-07-08T19:47:51+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.5.9" + "php": ">=8.2" }, - "platform-dev": [], - "plugin-api-version": "1.1.0" + "platform-dev": {}, + "plugin-api-version": "2.9.0" } diff --git a/tests/apps/python-flask/CHECKS b/tests/apps/python-flask/CHECKS deleted file mode 100644 index 6d804388b31..00000000000 --- a/tests/apps/python-flask/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ python/flask diff --git a/tests/apps/python-flask/app.json b/tests/apps/python-flask/app.json new file mode 100644 index 00000000000..3dd6806d22c --- /dev/null +++ b/tests/apps/python-flask/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "python/flask", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/python-flask/requirements.txt b/tests/apps/python-flask/requirements.txt index ba6801770f8..e656d4b652b 100644 --- a/tests/apps/python-flask/requirements.txt +++ b/tests/apps/python-flask/requirements.txt @@ -1,5 +1,5 @@ -Flask==2.0.2 -Jinja2==3.0.3 -Werkzeug==2.0.2 -gunicorn==20.1.0 +Flask==3.1.3 +Jinja2==3.1.6 +Werkzeug==3.1.8 +gunicorn==26.0.0 diff --git a/tests/apps/python/CHECKS b/tests/apps/python/CHECKS deleted file mode 100644 index 52a9225d1b7..00000000000 --- a/tests/apps/python/CHECKS +++ /dev/null @@ -1,5 +0,0 @@ -WAIT=2 # wait 2 seconds -TIMEOUT=5 # set timeout to 5 seconds -ATTEMPTS=2 # try 2 times - -/ python/http.server diff --git a/tests/apps/python/Procfile b/tests/apps/python/Procfile index 88d4d849fa9..8a57141eebd 100644 --- a/tests/apps/python/Procfile +++ b/tests/apps/python/Procfile @@ -3,11 +3,12 @@ ############################### # Procfile for development using the new threaded worker (scheduler, twitter stream and delayed job) -cron: python3 worker.py -web: python3 web.py # testing inline comment -worker: python3 worker.py +cron: python3 -u worker.py +web: python3 -u web.py first.Procfile # testing inline comment +worker: python3 -u worker.py custom: echo -n -release: python3 release.py +release: python3 -u release.py +task: python3 -u task.py test # Old version with separate processes (use this if you have issues with the threaded version) diff --git a/tests/apps/python/alt.Dockerfile b/tests/apps/python/alt.Dockerfile new file mode 100644 index 00000000000..0e9cfc44483 --- /dev/null +++ b/tests/apps/python/alt.Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.12.6-bookworm + +ARG BUILD_ARG=key + +WORKDIR /app + +COPY . /app + +RUN echo $BUILD_ARG > BUILD_ARG.contents diff --git a/tests/apps/python/app-5205a.json b/tests/apps/python/app-5205a.json new file mode 100644 index 00000000000..268a27a4c11 --- /dev/null +++ b/tests/apps/python/app-5205a.json @@ -0,0 +1,10 @@ +{ + "formation": { + "web": { + "quantity": 1 + }, + "worker": { + "quantity": 1 + } + } +} diff --git a/tests/apps/python/app-5205b.json b/tests/apps/python/app-5205b.json new file mode 100644 index 00000000000..b58838b9ea0 --- /dev/null +++ b/tests/apps/python/app-5205b.json @@ -0,0 +1,10 @@ +{ + "formation": { + "web": { + "quantity": 1 + }, + "cron": { + "quantity": 1 + } + } +} diff --git a/tests/apps/python/app-cnb-cron-procfile.json b/tests/apps/python/app-cnb-cron-procfile.json new file mode 100644 index 00000000000..892bc0bf1cf --- /dev/null +++ b/tests/apps/python/app-cnb-cron-procfile.json @@ -0,0 +1,21 @@ +{ + "cron": [ + { + "command": "task", + "schedule": "5 5 5 5 5" + } + ], + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "python/http.server", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + } +} diff --git a/tests/apps/python/app-cnb-cron.json b/tests/apps/python/app-cnb-cron.json new file mode 100644 index 00000000000..54d9aa7a1d9 --- /dev/null +++ b/tests/apps/python/app-cnb-cron.json @@ -0,0 +1,29 @@ +{ + "cron": [ + { + "command": "python3 task.py some cron task", + "schedule": "5 5 5 5 5" + } + ], + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "python/http.server", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, + "scripts": { + "dokku": { + // this is a comment for the postdeploy script + "postdeploy": "touch /workspace/postdeploy.test", + // this is a comment for the predeploy script + "predeploy": "touch /workspace/predeploy.test" + } + } +} diff --git a/tests/apps/python/app-cnb.json b/tests/apps/python/app-cnb.json new file mode 100644 index 00000000000..0d70124326f --- /dev/null +++ b/tests/apps/python/app-cnb.json @@ -0,0 +1,21 @@ +{ + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "python/http.server", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, + "scripts": { + "dokku": { + "postdeploy": "touch /workspace/postdeploy.test", + "predeploy": "touch /workspace/predeploy.test" + } + } +} diff --git a/tests/apps/python/app-cron-procfile.json b/tests/apps/python/app-cron-procfile.json new file mode 100644 index 00000000000..892bc0bf1cf --- /dev/null +++ b/tests/apps/python/app-cron-procfile.json @@ -0,0 +1,21 @@ +{ + "cron": [ + { + "command": "task", + "schedule": "5 5 5 5 5" + } + ], + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "python/http.server", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + } +} diff --git a/tests/apps/python/app-cron.json b/tests/apps/python/app-cron.json new file mode 100644 index 00000000000..89f2543ce66 --- /dev/null +++ b/tests/apps/python/app-cron.json @@ -0,0 +1,29 @@ +{ + "cron": [ + { + "command": "python3 task.py some cron task", + "schedule": "5 5 5 5 5" + } + ], + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "python/http.server", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, + "scripts": { + "dokku": { + // this is a comment for the postdeploy script + "postdeploy": "touch /app/postdeploy.test", + // this is a comment for the predeploy script + "predeploy": "touch /app/predeploy.test" + } + } +} \ No newline at end of file diff --git a/tests/apps/python/app-env-required.json b/tests/apps/python/app-env-required.json new file mode 100644 index 00000000000..c50087c7716 --- /dev/null +++ b/tests/apps/python/app-env-required.json @@ -0,0 +1,8 @@ +{ + "env": { + "REQUIRED_VAR": { + "description": "A required variable with no default", + "required": true + } + } +} diff --git a/tests/apps/python/app-env.json b/tests/apps/python/app-env.json new file mode 100644 index 00000000000..707768b888d --- /dev/null +++ b/tests/apps/python/app-env.json @@ -0,0 +1,27 @@ +{ + "env": { + "SIMPLE_VAR": "simple_value", + "OBJECT_VAR": { + "description": "A variable with a default value", + "value": "object_default" + }, + "SECRET_VAR": { + "description": "A generated secret", + "generator": "secret" + }, + "OPTIONAL_VAR": { + "description": "An optional variable", + "required": false + }, + "SYNC_VAR": { + "description": "A variable that syncs on every deploy", + "value": "sync_value", + "sync": true + } + }, + "scripts": { + "dokku": { + "predeploy": "echo ENV_CHECK: SIMPLE_VAR=$SIMPLE_VAR OBJECT_VAR=$OBJECT_VAR" + } + } +} diff --git a/tests/apps/python/app-heroku-postdeploy.json b/tests/apps/python/app-heroku-postdeploy.json new file mode 100644 index 00000000000..0daee49b0f6 --- /dev/null +++ b/tests/apps/python/app-heroku-postdeploy.json @@ -0,0 +1,24 @@ +{ + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "python/http.server", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, + "scripts": { + "dokku": { + // this is a comment for the postdeploy script + "postdeploy": "touch /app/postdeploy.test", + // this is a comment for the predeploy script + "predeploy": "touch /app/predeploy.test" + }, + "postdeploy": "touch /app/heroku-postdeploy.test" + } +} \ No newline at end of file diff --git a/tests/apps/python/app-traefik.json b/tests/apps/python/app-traefik.json new file mode 100644 index 00000000000..25c42a9d8a2 --- /dev/null +++ b/tests/apps/python/app-traefik.json @@ -0,0 +1,13 @@ +{ + "healthchecks": { + "web": [ + { + "name": "web readiness check", + "path": "/", + "timeout": 5, + "type": "readiness", + "wait": 2 + } + ] + } +} diff --git a/tests/apps/python/app.json b/tests/apps/python/app.json index 0dbdea3ec8b..246998f6fff 100644 --- a/tests/apps/python/app.json +++ b/tests/apps/python/app.json @@ -1,8 +1,23 @@ { + "healthchecks": { + "web": [ + { + "attempts": 2, + "content": "python/http.server", + "name": "check-1", + "path": "/", + "timeout": 5, + "type": "startup", + "wait": 2 + } + ] + }, "scripts": { "dokku": { - "predeploy": "touch /app/predeploy.test", - "postdeploy": "touch /app/postdeploy.test" + // this is a comment for the postdeploy script + "postdeploy": "touch /app/postdeploy.test", + // this is a comment for the predeploy script + "predeploy": "touch /app/predeploy.test" } } -} +} \ No newline at end of file diff --git a/tests/apps/python/app3.json b/tests/apps/python/app3.json new file mode 100644 index 00000000000..b729f3dca34 --- /dev/null +++ b/tests/apps/python/app3.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "dokku": { + "predeploy": "touch /app/predeploy3.test", + "postdeploy": "touch /app/postdeploy.test" + } + } +} diff --git a/tests/apps/python/cnb/1/bin/build b/tests/apps/python/cnb/1/bin/build index 24e91daceb9..29a58e38c3b 100755 --- a/tests/apps/python/cnb/1/bin/build +++ b/tests/apps/python/cnb/1/bin/build @@ -19,5 +19,5 @@ echo "TESTENV_1" >$bp_env_dir/TESTENV cat >"$layers_dir/launch.toml" <$bp_env_dir/TESTENV cat >"$layers_dir/launch.toml" <2.1.0' +gem 'sinatra', '~>4.2.1' gem 'thin' diff --git a/tests/apps/ruby/Gemfile.lock b/tests/apps/ruby/Gemfile.lock index a05fdae1655..e9985994f94 100644 --- a/tests/apps/ruby/Gemfile.lock +++ b/tests/apps/ruby/Gemfile.lock @@ -1,28 +1,59 @@ GEM remote: https://rubygems.org/ specs: - daemons (1.4.0) + base64 (0.3.0) + daemons (1.4.1) eventmachine (1.2.7) - mustermann (1.1.1) + logger (1.7.0) + mustermann (3.0.4) ruby2_keywords (~> 0.0.1) - rack (2.2.3) - rack-protection (2.1.0) - rack - ruby2_keywords (0.0.2) - sinatra (2.1.0) - mustermann (~> 1.0) - rack (~> 2.2) - rack-protection (= 2.1.0) + rack (3.2.6) + rack-protection (4.2.1) + base64 (>= 0.1.0) + logger (>= 1.6.0) + rack (>= 3.0.0, < 4) + rack-session (2.1.2) + base64 (>= 0.1.0) + rack (>= 3.0.0) + ruby2_keywords (0.0.5) + sinatra (4.2.1) + logger (>= 1.6.0) + mustermann (~> 3.0) + rack (>= 3.0.0, < 4) + rack-protection (= 4.2.1) + rack-session (>= 2.0.0, < 3) tilt (~> 2.0) - thin (1.8.1) + thin (2.0.1) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) - rack (>= 1, < 3) - tilt (2.0.10) + logger + rack (>= 1, < 4) + tilt (2.6.1) PLATFORMS + arm64-darwin-25 ruby DEPENDENCIES - sinatra (~> 2.1.0) + sinatra (~> 4.2.1) thin + +CHECKSUMS + base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b + daemons (1.4.1) sha256=8fc76d76faec669feb5e455d72f35bd4c46dc6735e28c420afb822fac1fa9a1d + eventmachine (1.2.7) sha256=994016e42aa041477ba9cff45cbe50de2047f25dd418eba003e84f0d16560972 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + mustermann (3.0.4) sha256=85fadcb6b3c6493a8b511b42426f904b7f27b282835502233dd154daab13aa22 + rack (3.2.6) sha256=5ed78e1f73b2e25679bec7d45ee2d4483cc4146eb1be0264fc4d94cb5ef212c2 + rack-protection (4.2.1) sha256=cf6e2842df8c55f5e4d1a4be015e603e19e9bc3a7178bae58949ccbb58558bac + rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8 + ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef + sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080 + thin (2.0.1) sha256=5bbde5648377f5c3864b5da7cd89a23b5c2d8d8bb9435719f6db49644bcdade9 + tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 + +RUBY VERSION + ruby 4.0.0 + +BUNDLED WITH + 4.0.3 diff --git a/tests/apps/ruby/app.json b/tests/apps/ruby/app.json new file mode 100644 index 00000000000..47f2b0f4bb6 --- /dev/null +++ b/tests/apps/ruby/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "Hello, world", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/scala/README.md b/tests/apps/scala/README.md index 67d57f50bea..177853c883d 100644 --- a/tests/apps/scala/README.md +++ b/tests/apps/scala/README.md @@ -9,10 +9,10 @@ This application support the [Getting Started with Scala on Heroku](https://devc Make sure you have Scala and sbt installed. Also, install the [Heroku Toolbelt](https://toolbelt.heroku.com/). ```sh -$ git clone https://github.com/heroku/scala-getting-started.git -$ cd scala-getting-started -$ sbt compile stage -$ foreman start web +git clone https://github.com/heroku/scala-getting-started.git +cd scala-getting-started +sbt compile stage +foreman start web ``` Your app should now be running on [localhost:5000](http://localhost:5000/). @@ -20,9 +20,9 @@ Your app should now be running on [localhost:5000](http://localhost:5000/). ## Deploying to Heroku ```sh -$ heroku create -$ git push heroku master -$ heroku open +heroku create +git push heroku master +heroku open ``` ## Documentation @@ -30,4 +30,3 @@ $ heroku open For more information about using Scala on Heroku, see these Dev Center articles: - [Scala on Heroku](https://devcenter.heroku.com/categories/scala) - diff --git a/tests/apps/static/CHECKS b/tests/apps/static/CHECKS deleted file mode 100644 index 925fdea2063..00000000000 --- a/tests/apps/static/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ Static Page diff --git a/tests/apps/static/app.json b/tests/apps/static/app.json new file mode 100644 index 00000000000..dc26105bba8 --- /dev/null +++ b/tests/apps/static/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "Static Page", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/zombies-buildpack/CHECKS b/tests/apps/zombies-buildpack/CHECKS deleted file mode 100644 index 52e1189c7de..00000000000 --- a/tests/apps/zombies-buildpack/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ go diff --git a/tests/apps/zombies-buildpack/app.json b/tests/apps/zombies-buildpack/app.json new file mode 100644 index 00000000000..36e31ada78f --- /dev/null +++ b/tests/apps/zombies-buildpack/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "go", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/zombies-buildpack/go.mod b/tests/apps/zombies-buildpack/go.mod index 7c980ac468f..3c966508e96 100644 --- a/tests/apps/zombies-buildpack/go.mod +++ b/tests/apps/zombies-buildpack/go.mod @@ -1,4 +1,3 @@ module dokku.me/web -// +heroku goVersion go1.16 -go 1.16 +go 1.26.2 diff --git a/tests/apps/zombies-dockerfile-no-tini/CHECKS b/tests/apps/zombies-dockerfile-no-tini/CHECKS deleted file mode 100644 index 52e1189c7de..00000000000 --- a/tests/apps/zombies-dockerfile-no-tini/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ go diff --git a/tests/apps/zombies-dockerfile-no-tini/Dockerfile b/tests/apps/zombies-dockerfile-no-tini/Dockerfile index f7963462642..1d83d4cb2e4 100644 --- a/tests/apps/zombies-dockerfile-no-tini/Dockerfile +++ b/tests/apps/zombies-dockerfile-no-tini/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.1 +FROM golang:1.26.5 ADD . /go/src/github.com/dokku/apps/web WORKDIR /go/src/github.com/dokku/apps/web diff --git a/tests/apps/zombies-dockerfile-no-tini/app.json b/tests/apps/zombies-dockerfile-no-tini/app.json new file mode 100644 index 00000000000..36e31ada78f --- /dev/null +++ b/tests/apps/zombies-dockerfile-no-tini/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "go", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/zombies-dockerfile-no-tini/go.mod b/tests/apps/zombies-dockerfile-no-tini/go.mod index a9f5c552a10..3c966508e96 100644 --- a/tests/apps/zombies-dockerfile-no-tini/go.mod +++ b/tests/apps/zombies-dockerfile-no-tini/go.mod @@ -1,3 +1,3 @@ module dokku.me/web -go 1.16 +go 1.26.2 diff --git a/tests/apps/zombies-dockerfile-tini/CHECKS b/tests/apps/zombies-dockerfile-tini/CHECKS deleted file mode 100644 index 52e1189c7de..00000000000 --- a/tests/apps/zombies-dockerfile-tini/CHECKS +++ /dev/null @@ -1 +0,0 @@ -/ go diff --git a/tests/apps/zombies-dockerfile-tini/Dockerfile b/tests/apps/zombies-dockerfile-tini/Dockerfile index 08b50ba5f42..90dcdc4e82b 100644 --- a/tests/apps/zombies-dockerfile-tini/Dockerfile +++ b/tests/apps/zombies-dockerfile-tini/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17.1 +FROM golang:1.26.5 ADD . /go/src/github.com/dokku/apps/web WORKDIR /go/src/github.com/dokku/apps/web diff --git a/tests/apps/zombies-dockerfile-tini/app.json b/tests/apps/zombies-dockerfile-tini/app.json new file mode 100644 index 00000000000..36e31ada78f --- /dev/null +++ b/tests/apps/zombies-dockerfile-tini/app.json @@ -0,0 +1,12 @@ +{ + "healthchecks": { + "web": [ + { + "content": "go", + "name": "check-1", + "path": "/", + "type": "startup" + } + ] + } +} \ No newline at end of file diff --git a/tests/apps/zombies-dockerfile-tini/go.mod b/tests/apps/zombies-dockerfile-tini/go.mod index a9f5c552a10..3c966508e96 100644 --- a/tests/apps/zombies-dockerfile-tini/go.mod +++ b/tests/apps/zombies-dockerfile-tini/go.mod @@ -1,3 +1,3 @@ module dokku.me/web -go 1.16 +go 1.26.2 diff --git a/tests/ci/setup.sh b/tests/ci/setup.sh index 231510e4e25..1cb1d4e8062 100755 --- a/tests/ci/setup.sh +++ b/tests/ci/setup.sh @@ -8,57 +8,70 @@ install_dependencies() { mkdir -p "$ROOT_DIR/build/" - DOCKER_IMAGE_LABELER_VERSION=$(grep DOCKER_IMAGE_LABELER_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) + DOCKER_IMAGE_LABELER_VERSION=$(jq -r --arg name docker-image-labeler '.dependencies[] | select(.name == $name) | .version' contrib/dependencies.json) DOCKER_IMAGE_LABELER_PACKAGE_NAME="docker-image-labeler_${DOCKER_IMAGE_LABELER_VERSION}_amd64.deb" if [[ ! -f "$ROOT_DIR/build/${DOCKER_IMAGE_LABELER_PACKAGE_NAME}" ]]; then - curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/bionic/docker-image-labeler_${DOCKER_IMAGE_LABELER_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${DOCKER_IMAGE_LABELER_PACKAGE_NAME}" + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/docker-image-labeler_${DOCKER_IMAGE_LABELER_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${DOCKER_IMAGE_LABELER_PACKAGE_NAME}" fi - HEROKUISH_VERSION=$(grep HEROKUISH_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) + DOCKER_CONTAINER_HEALTHCHECKER_VERSION=$(jq -r --arg name docker-container-healthchecker '.dependencies[] | select(.name == $name) | .version' contrib/dependencies.json) + DOCKER_CONTAINER_HEALTHCHECKER_PACKAGE_NAME="docker-container-healthchecker_${DOCKER_CONTAINER_HEALTHCHECKER_VERSION}_amd64.deb" + if [[ ! -f "$ROOT_DIR/build/${DOCKER_CONTAINER_HEALTHCHECKER_PACKAGE_NAME}" ]]; then + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/docker-container-healthchecker_${DOCKER_CONTAINER_HEALTHCHECKER_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${DOCKER_CONTAINER_HEALTHCHECKER_PACKAGE_NAME}" + fi + + HEROKUISH_VERSION=$(jq -r --arg name herokuish '.recommendations[] | select(.name == $name) | .version' contrib/dependencies.json) HEROKUISH_PACKAGE_NAME="herokuish_${HEROKUISH_VERSION}_amd64.deb" if [[ ! -f "$ROOT_DIR/build/${HEROKUISH_PACKAGE_NAME}" ]]; then - curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/bionic/herokuish_${HEROKUISH_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${HEROKUISH_PACKAGE_NAME}" + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/herokuish_${HEROKUISH_VERSION}_all.deb/download.deb" -o "$ROOT_DIR/build/${HEROKUISH_PACKAGE_NAME}" + fi + + LAMBDA_BUILDER_VERSION=$(jq -r --arg name lambda-builder '.dependencies[] | select(.name == $name) | .version' contrib/dependencies.json) + LAMBDA_BUILDER_PACKAGE_NAME="lambda-builder_${LAMBDA_BUILDER_VERSION}_amd64.deb" + if [[ ! -f "$ROOT_DIR/build/${LAMBDA_BUILDER_PACKAGE_NAME}" ]]; then + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/lambda-builder_${LAMBDA_BUILDER_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${LAMBDA_BUILDER_PACKAGE_NAME}" fi - NETRC_VERSION=$(grep NETRC_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) + NETRC_VERSION=$(jq -r --arg name netrc '.dependencies[] | select(.name == $name) | .version' contrib/dependencies.json) NETRC_PACKAGE_NAME="netrc_${NETRC_VERSION}_amd64.deb" if [[ ! -f "$ROOT_DIR/build/${NETRC_PACKAGE_NAME}" ]]; then - curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/bionic/netrc_${NETRC_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${NETRC_PACKAGE_NAME}" + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/netrc_${NETRC_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${NETRC_PACKAGE_NAME}" fi - PLUGN_VERSION=$(grep PLUGN_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) + PLUGN_VERSION=$(jq -r --arg name plugn '.predependencies[] | select(.name == $name) | .version' contrib/dependencies.json) PLUGN_PACKAGE_NAME="plugn_${PLUGN_VERSION}_amd64.deb" if [[ ! -f "$ROOT_DIR/build/${PLUGN_PACKAGE_NAME}" ]]; then - curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/bionic/plugn_${PLUGN_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${PLUGN_PACKAGE_NAME}" + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/plugn_${PLUGN_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${PLUGN_PACKAGE_NAME}" fi - SSHCOMMAND_VERSION=$(grep SSHCOMMAND_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) + SSHCOMMAND_VERSION=$(jq -r --arg name sshcommand '.dependencies[] | select(.name == $name) | .version' contrib/dependencies.json) SSHCOMMAND_PACKAGE_NAME="sshcommand_${SSHCOMMAND_VERSION}_amd64.deb" if [[ ! -f "$ROOT_DIR/build/${SSHCOMMAND_PACKAGE_NAME}" ]]; then - curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/bionic/sshcommand_${SSHCOMMAND_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${SSHCOMMAND_PACKAGE_NAME}" + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/sshcommand_${SSHCOMMAND_VERSION}_all.deb/download.deb" -o "$ROOT_DIR/build/${SSHCOMMAND_PACKAGE_NAME}" fi - SIGIL_VERSION=$(grep SIGIL_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) + SIGIL_VERSION=$(jq -r --arg name gliderlabs-sigil '.predependencies[] | select(.name == $name) | .version' contrib/dependencies.json) SIGIL_PACKAGE_NAME="gliderlabs-sigil_${SIGIL_VERSION}_amd64.deb" if [[ ! -f "$ROOT_DIR/build/${SIGIL_PACKAGE_NAME}" ]]; then - curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/bionic/gliderlabs-sigil_${SIGIL_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${SIGIL_PACKAGE_NAME}" + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/gliderlabs-sigil_${SIGIL_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${SIGIL_PACKAGE_NAME}" fi - PROCFILE_VERSION=$(grep PROCFILE_VERSION "${ROOT_DIR}/Makefile" | head -n1 | cut -d' ' -f3) + PROCFILE_VERSION=$(jq -r --arg name procfile-util '.dependencies[] | select(.name == $name) | .version' contrib/dependencies.json) PROCFILE_UTIL_PACKAGE_NAME="procfile-util_${PROCFILE_VERSION}_amd64.deb" if [[ ! -f "$ROOT_DIR/build/${PROCFILE_UTIL_PACKAGE_NAME}" ]]; then - curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/bionic/procfile-util_${PROCFILE_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${PROCFILE_UTIL_PACKAGE_NAME}" + curl -L "https://packagecloud.io/dokku/dokku/packages/ubuntu/noble/procfile-util_${PROCFILE_VERSION}_amd64.deb/download.deb" -o "$ROOT_DIR/build/${PROCFILE_UTIL_PACKAGE_NAME}" fi - sudo add-apt-repository -y ppa:nginx/stable sudo apt-get update -qq sudo apt-get -qq -y --no-install-recommends install cgroupfs-mount dos2unix jq nginx debconf-utils sudo cp "${ROOT_DIR}/tests/dhparam.pem" /etc/nginx/dhparam.pem ls -lah "${ROOT_DIR}/build/" sudo dpkg -i \ + "${ROOT_DIR}/build/$DOCKER_CONTAINER_HEALTHCHECKER_PACKAGE_NAME" \ "${ROOT_DIR}/build/$DOCKER_IMAGE_LABELER_PACKAGE_NAME" \ "${ROOT_DIR}/build/$HEROKUISH_PACKAGE_NAME" \ + "${ROOT_DIR}/build/$LAMBDA_BUILDER_PACKAGE_NAME" \ "${ROOT_DIR}/build/$NETRC_PACKAGE_NAME" \ "${ROOT_DIR}/build/$PLUGN_PACKAGE_NAME" \ "${ROOT_DIR}/build/$PROCFILE_UTIL_PACKAGE_NAME" \ @@ -74,14 +87,13 @@ build_dokku() { echo "=====> build_dokku on CIRCLE_NODE_INDEX: $CIRCLE_NODE_INDEX" "${ROOT_DIR}/contrib/release-dokku" build - cp "$(cat "${ROOT_DIR}/build/deb-filename")" "${ROOT_DIR}/build/dokku.deb" - cp "$(cat "${ROOT_DIR}/build/rpm-filename")" "${ROOT_DIR}/build/dokku.rpm" } install_dokku() { echo "=====> install_dokku on CIRCLE_NODE_INDEX: $CIRCLE_NODE_INDEX" - if [[ ! -f "${ROOT_DIR}/build/deb-filename" ]]; then + local architecture="$(dpkg --print-architecture)" + if [[ ! -f "${ROOT_DIR}/build/package/dokku-${architecture}.deb" ]]; then build_dokku fi @@ -98,9 +110,9 @@ EOF echo "-----> End debconf selections" sleep 5 - echo "-----> Start install $(cat "${ROOT_DIR}/build/deb-filename") via dpkg" - sudo TRACE=1 dpkg -i "$(cat "${ROOT_DIR}/build/deb-filename")" - echo "-----> End install $(cat "${ROOT_DIR}/build/deb-filename") via dpkg" + echo "-----> Start install ${ROOT_DIR}/build/package/dokku-${architecture}.deb via dpkg" + sudo dpkg -i "${ROOT_DIR}/build/package/dokku-${architecture}.deb" + echo "-----> End install ${ROOT_DIR}/build/package/dokku-${architecture}.deb via dpkg" } build_dokku_docker_image() { @@ -110,6 +122,7 @@ build_dokku_docker_image() { run_dokku_container() { echo "=====> run_dokku_container on CIRCLE_NODE_INDEX: $CIRCLE_NODE_INDEX" + echo "redirect: https://github.com/dokku/dokku-redirect.git" | sudo tee /var/lib/dokku/plugin-list docker run -d \ --env DOKKU_HOSTNAME=dokku.me \ --name dokku \ @@ -127,14 +140,14 @@ check_container() { echo "=====> check_container on CIRCLE_NODE_INDEX: $CIRCLE_NODE_INDEX" local is_up local cnt=0 - while true; do + while [ $cnt -lt 100 ]; do echo "$(date) [count: $cnt]: waiting for dokku startup" is_up=$( - docker exec dokku ps -ef | grep "/usr/sbin/sshd -D" >/dev/null 2>&1 + docker exec dokku ps -ef | grep "/usr/sbin/sshd -D" &>/dev/null echo $? ) + echo "" && docker logs dokku if [[ $is_up -eq 0 ]]; then - echo "" && docker logs dokku break fi sleep 2 @@ -153,7 +166,6 @@ create_package() { build_dokku } -# shellcheck disable=SC2120 setup_circle() { install_dependencies install_dokku @@ -182,12 +194,10 @@ case "$1" in run_dokku_container ;; build) - # shellcheck disable=SC2119 create_package exit $? ;; *) - # shellcheck disable=SC2119 setup_circle exit $? ;; diff --git a/tests/ci/unit_test_runner.sh b/tests/ci/unit_test_runner.sh index 53f18d6d27c..4d39c10c89c 100755 --- a/tests/ci/unit_test_runner.sh +++ b/tests/ci/unit_test_runner.sh @@ -21,7 +21,6 @@ is_number "$BATCH_NUM" || usage TESTS=$(find "$(dirname "$0")/../unit" -maxdepth 1 -name "${BATCH_NUM}0*.bats" | sort -n | xargs) echo "running the following tests $TESTS" -# shellcheck disable=SC2086 for test in $TESTS; do echo $test starttest=$(date +%s) diff --git a/tests/image/healthcheck.sh b/tests/image/healthcheck.sh new file mode 100755 index 00000000000..6fb2399b4e4 --- /dev/null +++ b/tests/image/healthcheck.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +set -euo pipefail + +IMAGE_TAG="${IMAGE_TAG:-dokku/dokku:latest}" +CONTAINER_NAME="${CONTAINER_NAME:-dokku-healthcheck-smoke}" +START_TIMEOUT_SECONDS="${START_TIMEOUT_SECONDS:-360}" +UNHEALTHY_TIMEOUT_SECONDS="${UNHEALTHY_TIMEOUT_SECONDS:-180}" + +log-info() { + echo "-----> $*" +} + +log-fail() { + echo " ! $*" >&2 + exit 1 +} + +cleanup() { + docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +require-image() { + if ! docker image inspect "$IMAGE_TAG" >/dev/null 2>&1; then + log-fail "Image '$IMAGE_TAG' not found in local docker daemon. Build it first (eg. 'docker build -t $IMAGE_TAG .')." + fi +} + +start-container() { + log-info "Starting container '$CONTAINER_NAME' from image '$IMAGE_TAG'" + docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true + docker run -d \ + --name "$CONTAINER_NAME" \ + --env DOKKU_HOSTNAME=dokku.me \ + -v /var/run/docker.sock:/var/run/docker.sock \ + "$IMAGE_TAG" >/dev/null +} + +wait-for-catchall-vhost() { + local deadline=$((SECONDS + 30)) + while ((SECONDS < deadline)); do + if docker exec "$CONTAINER_NAME" test -f /etc/nginx/conf.d/00-default-vhost.conf; then + return 0 + fi + sleep 1 + done + + log-fail "Catch-all default vhost /etc/nginx/conf.d/00-default-vhost.conf was never created. \ +Likely cause: a stale build/package/dokku-*.deb that predates the catch-all template shipped in \ +plugins/nginx-vhosts/templates/default-site.conf. Rebuild the deb before running this test." +} + +wait-for-healthy() { + local deadline=$((SECONDS + START_TIMEOUT_SECONDS)) + local status + while ((SECONDS < deadline)); do + status=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "missing") + if [[ "$status" == "healthy" ]]; then + log-info "Container reached healthy status" + return 0 + fi + sleep 5 + done + + log-info "Container failed to become healthy within ${START_TIMEOUT_SECONDS}s. Last logs:" + docker logs --tail 100 "$CONTAINER_NAME" || true + log-fail "Container '$CONTAINER_NAME' did not reach healthy status (last status: ${status:-unknown})" +} + +assert-endpoint-ok() { + log-info "Verifying /_dokku/health returns ok from inside the container" + local body + body=$(docker exec "$CONTAINER_NAME" curl -fsS http://127.0.0.1:18080/_dokku/health) + if [[ "$body" != "ok" ]]; then + log-fail "Expected health endpoint body 'ok', got: ${body}" + fi +} + +assert-port-not-published() { + log-info "Verifying port 18080 is not published to the host" + local ports + ports=$(docker inspect --format='{{json .NetworkSettings.Ports}}' "$CONTAINER_NAME") + if echo "$ports" | grep -q "18080/tcp"; then + log-fail "Port 18080/tcp should not be published to the host. Inspect output: $ports" + fi + + if curl --max-time 2 -fsS http://127.0.0.1:18080/_dokku/health >/dev/null 2>&1; then + log-fail "Health endpoint is reachable from the host on 127.0.0.1:18080 - it must be loopback-only inside the container." + fi +} + +assert-unhealthy-after-sentinel-removed() { + log-info "Removing /var/run/dokku/ready and confirming endpoint returns failure" + docker exec "$CONTAINER_NAME" rm -f /var/run/dokku/ready + + local exit_code=0 + docker exec "$CONTAINER_NAME" curl -fsS http://127.0.0.1:18080/_dokku/health >/dev/null 2>&1 || exit_code=$? + if ((exit_code == 0)); then + log-fail "Health endpoint still returned success after the readiness sentinel was removed" + fi + + log-info "Waiting for docker to flip the health status to unhealthy" + local deadline=$((SECONDS + UNHEALTHY_TIMEOUT_SECONDS)) + local status + while ((SECONDS < deadline)); do + status=$(docker inspect --format='{{.State.Health.Status}}' "$CONTAINER_NAME" 2>/dev/null || echo "missing") + if [[ "$status" == "unhealthy" ]]; then + log-info "Container flipped to unhealthy" + return 0 + fi + sleep 5 + done + + log-fail "Container did not flip to unhealthy within ${UNHEALTHY_TIMEOUT_SECONDS}s (last status: ${status:-unknown})" +} + +main() { + require-image + start-container + wait-for-catchall-vhost + wait-for-healthy + assert-endpoint-ok + assert-port-not-published + assert-unhealthy-after-sentinel-removed + log-info "All healthcheck smoke tests passed." +} + +main "$@" diff --git a/tests/shellcheck-to-junit b/tests/shellcheck-to-junit index d006f7fb46c..057f1e0303c 100755 --- a/tests/shellcheck-to-junit +++ b/tests/shellcheck-to-junit @@ -45,11 +45,17 @@ def process_lines(lines): new_issues = [] code = None - RE_VIOLATION = re.compile(r"\^-- (SC[\w]+): (.*)") + RE_VIOLATION = re.compile(r"\^(-)+(\^)? (SC[\w]+) \((\w+)\): (.*)") + HTTP_VIOLATION = re.compile(r"^https?://") + line_num = 0 + skip_next_line = False for line in lines: # start a new block - if line == '': + if skip_next_line: + skip_next_line = False + continue + elif line == '': if current_file is not None: file_data = files.get(current_file, {}) files[current_file] = file_data @@ -74,11 +80,19 @@ def process_lines(lines): code = None elif code is None and len(new_issues) == 0: code = line + elif line.startswith('Did you mean:') or line.startswith('For more information:') or HTTP_VIOLATION.match(line.strip()): + skip_next_line = True + pass else: match = RE_VIOLATION.match(line.strip()) if not match: print('Error: Issue parsing line "{0}"'.format(line.strip())) else: + next_line_num = line_num + 2 + if next_line_num < len(lines): + next_line = lines[next_line_num] + if next_line.startswith('Did you mean:'): + line += '\n' + lines[next_line_num] + '\n' + lines[next_line_num+1] new_issues.append({ 'shellcheck_id': match.group(1), 'message': match.group(2), @@ -86,7 +100,7 @@ def process_lines(lines): }) previous_line = line - + line_num += 1 return files @@ -104,6 +118,9 @@ def output_junit(files, args): with open(args.files, 'r') as f: tests = len(f.readlines()) + if tests < failures: + tests = failures + root = ElementTree.Element("testsuite", name="shellcheck", tests="{0}".format(tests), diff --git a/tests/test_deploy b/tests/test_deploy index 1ae817c68d9..651e16098b1 100755 --- a/tests/test_deploy +++ b/tests/test_deploy @@ -10,9 +10,11 @@ BRANCH="${5:-master}" REMOTE="dokku@$TARGET" REPO="test-$(basename "$APP")-$RANDOM" +ssh "$REMOTE" events:on + destroy_app() { - # shellcheck disable=SC2029 - echo "$REPO" | ssh "$REMOTE" apps:destroy "$REPO" + ssh "$REMOTE" apps:destroy "$REPO" --force + ssh "$REMOTE" events:off } failed() { @@ -43,10 +45,11 @@ git add . [[ -x pre-commit ]] && ./pre-commit "$REMOTE" "$REPO" git commit -m 'initial commit' +current_branch="$(git rev-parse --abbrev-ref HEAD)" if [[ "$SHOULD_FAIL" == true ]]; then - git push target "master:$BRANCH" && succeeded git-push + git push target "$current_branch:$BRANCH" && succeeded git-push else - git push target "master:$BRANCH" || failed git-push + git push target "$current_branch:$BRANCH" || failed git-push fi if [[ -x post-deploy ]]; then ./post-deploy "$REMOTE" "$REPO" || failed post-deploy @@ -58,7 +61,9 @@ if [[ "$SHOULD_FAIL" == true ]]; then exit 0 fi -URL=$(dokku url "$REPO")$FORWARDED_PORT +ssh "$REMOTE" events +ssh "$REMOTE" apps:list +URL=$(ssh "$REMOTE" url "$REPO")$FORWARDED_PORT sleep 2 if (./check_deploy "$URL"); then echo "-----> Deploy success!" diff --git a/tests/unit/app-json-2.bats b/tests/unit/app-json-2.bats index c3fdd2d679a..bacf838dcc2 100644 --- a/tests/unit/app-json-2.bats +++ b/tests/unit/app-json-2.bats @@ -27,7 +27,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output 'null' + assert_output '["docker-entrypoint.sh"]' } @test "(app-json) app.json dockerfile release" { @@ -45,7 +45,7 @@ teardown() { } @test "(app-json) app.json dockerfile entrypoint release" { - run deploy_app dockerfile-entrypoint dokku@dokku.me:$TEST_APP add_release_command + run deploy_app dockerfile-entrypoint "dokku@$DOKKU_DOMAIN:$TEST_APP" add_release_command echo "output: $output" echo "status: $status" assert_success @@ -65,4 +65,3 @@ teardown() { echo "status: $status" assert_success } - diff --git a/tests/unit/app-json-3.bats b/tests/unit/app-json-3.bats new file mode 100644 index 00000000000..efc8de7f810 --- /dev/null +++ b/tests/unit/app-json-3.bats @@ -0,0 +1,83 @@ +#!/usr/bin/env bats +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + destroy_app + global_teardown +} + +@test "(app-json) persist scale" { + local CUSTOM_TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM + + CUSTOM_TMP="$CUSTOM_TMP" run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" persist_scale_callback_a + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "web: 1" + assert_output_contains "worker: 1" + assert_output_contains "cron: 0" 0 + + run persist_scale_callback_b "$TEST_APP" "$CUSTOM_TMP" + echo "output: $output" + echo "status: $status" + assert_success + + run git -C "$CUSTOM_TMP" add app.json Procfile + echo "output: $output" + echo "status: $status" + assert_success + + run git -C "$CUSTOM_TMP" commit -m 'Update scaling parameters' + echo "output: $output" + echo "status: $status" + assert_success + + run git -C "$CUSTOM_TMP" push target master:master + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "web: 1" + assert_output_contains "worker: 1" 0 + assert_output_contains "worker: 0" 0 + assert_output_contains "cron: 1" 1 +} + +persist_scale_callback_a() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + + rm "$APP_REPO_DIR/Procfile" + touch "$APP_REPO_DIR/Procfile" + echo "web: python3 web.py" >>"$APP_REPO_DIR/Procfile" + echo "worker: python3 worker.py" >>"$APP_REPO_DIR/Procfile" + mv "$APP_REPO_DIR/app-5205a.json" "$APP_REPO_DIR/app.json" +} + +persist_scale_callback_b() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + + rm "$APP_REPO_DIR/Procfile" + touch "$APP_REPO_DIR/Procfile" + echo "web: python3 web.py" >>"$APP_REPO_DIR/Procfile" + echo "cron: python3 worker.py" >>"$APP_REPO_DIR/Procfile" + mv "$APP_REPO_DIR/app-5205b.json" "$APP_REPO_DIR/app.json" +} diff --git a/tests/unit/app-json-4.bats b/tests/unit/app-json-4.bats new file mode 100644 index 00000000000..315d1c7770f --- /dev/null +++ b/tests/unit/app-json-4.bats @@ -0,0 +1,125 @@ +#!/usr/bin/env bats +load test_helper + +setup_file() { + install_pack +} + +setup() { + global_setup + create_app +} + +teardown() { + destroy_app + global_teardown +} + +@test "(app-json) app.json failing dokku.predeploy" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_failing_dokku_predeploy + echo "output: $output" + echo "status: $status" + assert_output_contains "Execution of predeploy task failed" + assert_failure +} + +@test "(app-json) app.json failing dokku.postdeploy" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_failing_dokku_postdeploy + echo "output: $output" + echo "status: $status" + assert_output_contains "Execution of postdeploy task failed" + assert_failure +} + +@test "(app-json) app.json failing postdeploy" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_failing_postdeploy + echo "output: $output" + echo "status: $status" + assert_output_contains "Execution of heroku.postdeploy task failed" + assert_failure +} + +@test "(app-json) app.json failing release" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_failing_release + echo "output: $output" + echo "status: $status" + assert_output_contains "Execution of release task failed" + assert_failure +} + +add_failing_dokku_predeploy() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + + cat >"$APP_REPO_DIR/app.json" <"$APP_REPO_DIR/app.json" <"$APP_REPO_DIR/app.json" <"$APP_REPO_DIR/Procfile" </dev/null 2>&1 || true + dokku git:set --global archive-max-files "" >/dev/null 2>&1 || true + destroy_app + global_teardown +} + +create_absolute_symlink_tar() { + local OUTPUT="$1" FORMAT="${2:-tar}" + python3 - "$OUTPUT" "$FORMAT" <<'PY' +import io, sys, tarfile +output, fmt = sys.argv[1], sys.argv[2] +mode = "w:gz" if fmt == "tar.gz" else "w" +with tarfile.open(output, mode) as t: + link = tarfile.TarInfo("pwn") + link.type = tarfile.SYMTYPE + link.linkname = "/tmp" + t.addfile(link) + payload = b"canary content\n" + fi = tarfile.TarInfo("pwn/dokku-archive-security-canary.txt") + fi.size = len(payload) + t.addfile(fi, io.BytesIO(payload)) + readme = b"# dummy\n" + ri = tarfile.TarInfo("README.md") + ri.size = len(readme) + t.addfile(ri, io.BytesIO(readme)) +PY +} + +create_relative_traversal_symlink_tar() { + local OUTPUT="$1" FORMAT="${2:-tar}" + python3 - "$OUTPUT" "$FORMAT" <<'PY' +import io, sys, tarfile +output, fmt = sys.argv[1], sys.argv[2] +mode = "w:gz" if fmt == "tar.gz" else "w" +with tarfile.open(output, mode) as t: + link = tarfile.TarInfo("pwn") + link.type = tarfile.SYMTYPE + link.linkname = "../../../../tmp" + t.addfile(link) + payload = b"canary content\n" + fi = tarfile.TarInfo("pwn/dokku-archive-security-canary.txt") + fi.size = len(payload) + t.addfile(fi, io.BytesIO(payload)) +PY +} + +create_absolute_path_tar() { + local OUTPUT="$1" FORMAT="${2:-tar}" + python3 - "$OUTPUT" "$FORMAT" <<'PY' +import io, sys, tarfile +output, fmt = sys.argv[1], sys.argv[2] +mode = "w:gz" if fmt == "tar.gz" else "w" +with tarfile.open(output, mode) as t: + payload = b"absolute path payload\n" + fi = tarfile.TarInfo("/tmp/dokku-archive-security-canary.txt") + fi.size = len(payload) + t.addfile(fi, io.BytesIO(payload)) +PY +} + +create_traversal_path_tar() { + local OUTPUT="$1" FORMAT="${2:-tar}" + python3 - "$OUTPUT" "$FORMAT" <<'PY' +import io, sys, tarfile +output, fmt = sys.argv[1], sys.argv[2] +mode = "w:gz" if fmt == "tar.gz" else "w" +with tarfile.open(output, mode) as t: + payload = b"traversal payload\n" + fi = tarfile.TarInfo("../../../tmp/dokku-archive-security-canary.txt") + fi.size = len(payload) + t.addfile(fi, io.BytesIO(payload)) +PY +} + +create_safe_multi_file_tar() { + local OUTPUT="$1" + python3 - "$OUTPUT" <<'PY' +import io, sys, tarfile +output = sys.argv[1] +with tarfile.open(output, "w") as t: + for name in ("README.md", "Procfile", "app.py", "requirements.txt"): + payload = (name + " contents\n").encode() + ti = tarfile.TarInfo(name) + ti.size = len(payload) + t.addfile(ti, io.BytesIO(payload)) +PY +} + +create_evil_certs_tar() { + local OUTPUT="$1" + python3 - "$OUTPUT" <<'PY' +import io, sys, tarfile +output = sys.argv[1] +with tarfile.open(output, "w") as t: + link = tarfile.TarInfo("pwn") + link.type = tarfile.SYMTYPE + link.linkname = "/tmp" + t.addfile(link) + payload = b"canary content\n" + fi = tarfile.TarInfo("pwn/dokku-archive-security-canary.txt") + fi.size = len(payload) + t.addfile(fi, io.BytesIO(payload)) + crt = b"-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----\n" + key = b"-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----\n" + tcrt = tarfile.TarInfo("server.crt") + tcrt.size = len(crt) + t.addfile(tcrt, io.BytesIO(crt)) + tkey = tarfile.TarInfo("server.key") + tkey.size = len(key) + t.addfile(tkey, io.BytesIO(key)) +PY +} + +@test "(archive-security) git:from-archive rejects tar with absolute symlink target" { + create_absolute_symlink_tar "$ARCHIVE_TMP_DIR/evil.tar" "tar" + run /bin/bash -c "cat $ARCHIVE_TMP_DIR/evil.tar | dokku git:from-archive $TEST_APP --" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "absolute targets" + [[ ! -f /tmp/dokku-archive-security-canary.txt ]] +} + +@test "(archive-security) git:from-archive rejects tar.gz with absolute symlink target" { + create_absolute_symlink_tar "$ARCHIVE_TMP_DIR/evil.tar.gz" "tar.gz" + run /bin/bash -c "cat $ARCHIVE_TMP_DIR/evil.tar.gz | dokku git:from-archive --archive-type tar.gz $TEST_APP --" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "absolute targets" + [[ ! -f /tmp/dokku-archive-security-canary.txt ]] +} + +@test "(archive-security) git:from-archive rejects tar with relative traversal symlink" { + create_relative_traversal_symlink_tar "$ARCHIVE_TMP_DIR/evil.tar" "tar" + run /bin/bash -c "cat $ARCHIVE_TMP_DIR/evil.tar | dokku git:from-archive $TEST_APP --" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "symlinks pointing outside extraction directory" + [[ ! -f /tmp/dokku-archive-security-canary.txt ]] +} + +@test "(archive-security) git:from-archive rejects tar with absolute paths" { + create_absolute_path_tar "$ARCHIVE_TMP_DIR/evil.tar" "tar" + run /bin/bash -c "cat $ARCHIVE_TMP_DIR/evil.tar | dokku git:from-archive $TEST_APP --" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "absolute paths" + [[ ! -f /tmp/dokku-archive-security-canary.txt ]] +} + +@test "(archive-security) git:from-archive rejects tar with parent traversal" { + create_traversal_path_tar "$ARCHIVE_TMP_DIR/evil.tar" "tar" + run /bin/bash -c "cat $ARCHIVE_TMP_DIR/evil.tar | dokku git:from-archive $TEST_APP --" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Archive contains entries with parent directory traversal" + [[ ! -f /tmp/dokku-archive-security-canary.txt ]] +} + +@test "(archive-security) certs:add rejects tar with absolute symlink target" { + create_evil_certs_tar "$ARCHIVE_TMP_DIR/evil-certs.tar" + run /bin/bash -c "cat $ARCHIVE_TMP_DIR/evil-certs.tar | dokku certs:add $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "absolute targets" + [[ ! -f /tmp/dokku-archive-security-canary.txt ]] +} + +@test "(archive-security) certs:add still works with legitimate tarball" { + run /bin/bash -c "dokku certs:add $TEST_APP < $BATS_TEST_DIRNAME/server_ssl.tar" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(archive-security) git:from-archive enforces archive-max-size property" { + create_safe_multi_file_tar "$ARCHIVE_TMP_DIR/safe.tar" + run /bin/bash -c "dokku git:set --global archive-max-size 1" + assert_success + + run /bin/bash -c "cat $ARCHIVE_TMP_DIR/safe.tar | dokku git:from-archive $TEST_APP --" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Archive exceeds maximum allowed size of 1 bytes" + + run /bin/bash -c "dokku git:set --global archive-max-size" + assert_success +} + +@test "(archive-security) git:from-archive enforces archive-max-files property" { + create_safe_multi_file_tar "$ARCHIVE_TMP_DIR/safe.tar" + run /bin/bash -c "dokku git:set --global archive-max-files 1" + assert_success + + run /bin/bash -c "cat $ARCHIVE_TMP_DIR/safe.tar | dokku git:from-archive $TEST_APP --" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "exceeds the maximum of 1" + + run /bin/bash -c "dokku git:set --global archive-max-files" + assert_success +} + +@test "(archive-security) git:report exposes archive-max-size and archive-max-files" { + run /bin/bash -c "dokku git:report --global --git-global-archive-max-size" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report --global --git-computed-archive-max-size" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1073741824" + + run /bin/bash -c "dokku git:report --global --git-global-archive-max-files" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report --global --git-computed-archive-max-files" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "10000" + + run /bin/bash -c "dokku git:set --global archive-max-size 2147483648" + assert_success + run /bin/bash -c "dokku git:report --global --git-global-archive-max-size" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2147483648" + + run /bin/bash -c "dokku git:report --global --git-computed-archive-max-size" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2147483648" + + run /bin/bash -c "dokku git:set --global archive-max-size" + assert_success +} diff --git a/tests/unit/build-env.bats b/tests/unit/build-env.bats index 8b59c61c5c3..3f5e87ff8a4 100644 --- a/tests/unit/build-env.bats +++ b/tests/unit/build-env.bats @@ -18,7 +18,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku config $TEST_APP" assert_success } @@ -34,7 +37,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku config:get --global CURL_CONNECT_TIMEOUT | grep 90" echo "output: $output" echo "status: $status" @@ -59,39 +65,18 @@ teardown() { } @test "(build-env) buildpack deploy with Dockerfile" { - run /bin/bash -c "dokku config:set --no-restart $TEST_APP BUILDPACK_URL='https://github.com/heroku/heroku-buildpack-nodejs'" + run /bin/bash -c "dokku config:set --no-restart $TEST_APP BUILDPACK_URL='https://github.com/dokku/heroku-buildpack-null'" echo "output: $output" echo "status: $status" assert_success - deploy_app dockerfile - run /bin/bash -c "dokku --quiet config:get $TEST_APP DOKKU_APP_TYPE" + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP move_dockerfile_into_place echo "output: $output" echo "status: $status" - assert_output "herokuish" -} - -@test "(build-env) DOKKU_ROOT cache bind is used by default" { - deploy_app - - BUILD_CID=$(docker ps -a | grep $TEST_APP | grep /bin/bash | awk '{print $1}' | head -n1) - run /bin/bash -c "docker inspect --format '{{ .HostConfig.Binds }}' $BUILD_CID | tr -d '[]' | cut -f1 -d:" - echo "output: $output" - echo "status: $status" - assert_output "$DOKKU_ROOT/$TEST_APP/cache" -} - -@test "(build-env) DOKKU_HOST_ROOT cache bind is used if set" { - TMP_ROOT=$(mktemp -d) - mkdir -p $DOKKU_ROOT/.dokkurc - echo export DOKKU_HOST_ROOT="$TMP_ROOT" > $DOKKU_ROOT/.dokkurc/HOST_ROOT - DOKKU_HOST_ROOT="$TMP_ROOT" deploy_app + assert_success - BUILD_CID=$(docker ps -a | grep $TEST_APP | grep /bin/bash | awk '{print $1}' | head -n1) - run /bin/bash -c "docker inspect --format '{{ .HostConfig.Binds }}' $BUILD_CID | tr -d '[]' | cut -f1 -d:" + run /bin/bash -c "dokku builder:report $TEST_APP --builder-detected" echo "output: $output" echo "status: $status" - assert_output "$TMP_ROOT/$TEST_APP/cache" - - rm -rf $TMP_ROOT $DOKKU_ROOT/.dokkurc/HOST_ROOT + assert_output "herokuish" } diff --git a/tests/unit/builder-dockerfile.bats b/tests/unit/builder-dockerfile.bats index b9138e16f7e..93d662f0048 100644 --- a/tests/unit/builder-dockerfile.bats +++ b/tests/unit/builder-dockerfile.bats @@ -4,12 +4,138 @@ load test_helper setup() { create_app + DOCKERFILE="$BATS_TMPDIR/Dockerfile" } teardown() { + rm -f "$DOCKERFILE" destroy_app } +@test "(builder-dockerfile:report) info-flag works before deploy" { + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-dockerfile-path" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:set $TEST_APP dockerfile-path second.Dockerfile" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-dockerfile-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "second.Dockerfile" + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(builder-dockerfile:report) --global --format json" { + run /bin/bash -c "dokku builder-dockerfile:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:report --global --format json | jq -r '.\"global-dockerfile-path\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku builder-dockerfile:report --global --format json | jq -r 'has(\"dockerfile-path\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder-dockerfile:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global builder-dockerfile information" + + run /bin/bash -c "dokku builder-dockerfile:report --global --builder-dockerfile-global-dockerfile-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku builder-dockerfile:set --global dockerfile-path Dockerfile.custom" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:report --global --builder-dockerfile-global-dockerfile-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "Dockerfile.custom" + + run /bin/bash -c "dokku builder-dockerfile:set --global dockerfile-path" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(builder-dockerfile:report) dockerfile-path raw vs computed vs global" { + run /bin/bash -c "dokku builder-dockerfile:set --global dockerfile-path" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-dockerfile-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-global-dockerfile-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-computed-dockerfile-path" + assert_success + assert_output "Dockerfile" + + run /bin/bash -c "dokku builder-dockerfile:set --global dockerfile-path Dockerfile.global" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-global-dockerfile-path" + assert_success + assert_output "Dockerfile.global" + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-computed-dockerfile-path" + assert_success + assert_output "Dockerfile.global" + + run /bin/bash -c "dokku builder-dockerfile:set $TEST_APP dockerfile-path Dockerfile.app" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-dockerfile-path" + assert_success + assert_output "Dockerfile.app" + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-global-dockerfile-path" + assert_success + assert_output "Dockerfile.global" + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-computed-dockerfile-path" + assert_success + assert_output "Dockerfile.app" + + run /bin/bash -c "dokku builder-dockerfile:set $TEST_APP dockerfile-path" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:set --global dockerfile-path" + assert_success + + run /bin/bash -c "dokku builder-dockerfile:report $TEST_APP --builder-dockerfile-computed-dockerfile-path" + assert_success + assert_output "Dockerfile" +} + @test "(builder-dockerfile:set)" { run deploy_app dockerfile echo "output: $output" @@ -48,3 +174,183 @@ teardown() { assert_success assert_output_contains 'echo hi' 0 } + +@test "(builder-dockerfile) exec" { + run /bin/bash -c "dokku builder-dockerfile:set $TEST_APP dockerfile-path exec.Dockerfile" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku app-json:set $TEST_APP appjson-path app.json-fake" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:set $TEST_APP procfile-path exec.Procfile" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run $TEST_APP task" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Found 'task' in Procfile, running that command" + assert_output_contains 'hi dokku' +} + +@test "(builder-dockerfile) config export" { + run /bin/bash -c "dokku config:set $TEST_APP GITHUB_TOKEN=custom-value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP build '--build-arg GITHUB_TOKEN'" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "TOKEN is: custom-value" 2 +} + +@test "(builder-dockerfile) port exposure (dockerfile raw port)" { + source "$PLUGIN_CORE_AVAILABLE_PATH/builder-dockerfile/internal-functions" + cat <$DOCKERFILE +EXPOSE 3001/udp +EXPOSE 3003 +EXPOSE 3000/tcp +EOF + run fn-builder-dockerfile-get-ports-from-dockerfile $DOCKERFILE + echo "output: $output" + echo "status: $status" + assert_output "3001/udp 3003 3000/tcp" +} + +@test "(builder-dockerfile) port exposure (dockerfile tcp port)" { + source "$PLUGIN_CORE_AVAILABLE_PATH/builder-dockerfile/internal-functions" + cat <$DOCKERFILE +EXPOSE 3001/udp +EXPOSE 3000/tcp +EXPOSE 3003 +EOF + run fn-builder-dockerfile-get-ports-from-dockerfile $DOCKERFILE + echo "output: $output" + echo "status: $status" + assert_output "3001/udp 3000/tcp 3003" +} + +@test "(builder-dockerfile) core-post-extract renames the configured dockerfile" { + echo "FROM scratch" >"$BATS_TEST_TMPDIR/second.Dockerfile" + + run /bin/bash -c "dokku builder-dockerfile:set $TEST_APP dockerfile-path second.Dockerfile" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugin_script builder-dockerfile core-post-extract "$TEST_APP" "$BATS_TEST_TMPDIR" HEAD + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $BATS_TEST_TMPDIR/Dockerfile" + assert_success + + run /bin/bash -c "test ! -e $BATS_TEST_TMPDIR/second.Dockerfile" + assert_success +} + +@test "(builder-dockerfile) ps:rebuild fetches files from image" { + run /bin/bash -c "dokku --trace git:from-image $TEST_APP dokku/smoke-test-app:dockerfile" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(builder-dockerfile) run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected dockerfile" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run $TEST_APP python3 task.py test" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku --quiet run $TEST_APP task" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY=fjdkslafjdk" +} + +@test "(builder-dockerfile) cron:run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected dockerfile" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_wrapper + echo "output: $output" + echo "status: $status" + assert_success + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'some', 'cron', 'task']" +} + +cron_run_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + convert_to_dockerfile "$APP" "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cron.json" "$APP_REPO_DIR/app.json" +} diff --git a/tests/unit/builder-herokuish.bats b/tests/unit/builder-herokuish.bats index b4fda84dd0a..c6a7ef2ed2c 100644 --- a/tests/unit/builder-herokuish.bats +++ b/tests/unit/builder-herokuish.bats @@ -10,10 +10,254 @@ teardown() { destroy_app } +@test "(builder-herokuish:report) info-flag works before deploy" { + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-allowed" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-allowed" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(builder-herokuish:report) --global --builder-herokuish-global-allowed" { + run /bin/bash -c "dokku builder-herokuish:set --global allowed false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:report --global --builder-herokuish-global-allowed" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder-herokuish:set --global allowed" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(builder-herokuish:report) allowed raw vs computed vs global" { + local default_allowed="true" + [[ "$(dpkg --print-architecture 2>/dev/null || true)" != "amd64" ]] && default_allowed="false" + + run /bin/bash -c "dokku builder-herokuish:set --global allowed" + assert_success + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-allowed" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-global-allowed" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-computed-allowed" + assert_success + assert_output "$default_allowed" + + run /bin/bash -c "dokku builder-herokuish:set --global allowed false" + assert_success + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-global-allowed" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-computed-allowed" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + assert_success + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-allowed" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-global-allowed" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP --builder-herokuish-computed-allowed" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed" + assert_success + + run /bin/bash -c "dokku builder-herokuish:set --global allowed" + assert_success +} + @test "(builder-herouish:build .env)" { - run deploy_app python dokku@dokku.me:$TEST_APP + run /bin/bash -c "dokku docker-options:add $TEST_APP build '--label=com.dokku.build-test=safe'" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP echo "output: $output" echo "status: $status" assert_success assert_output_contains 'DOTENV_KEY=some_value' -} \ No newline at end of file +} + +@test "(builder-herokuish) builder-herokuish:set allowed" { + if [[ "$(dpkg --print-architecture 2>/dev/null || true)" == "amd64" ]]; then + skip "this test cannot be performed accurately on amd64 as it tests whether we can enable the plugin on arm64" + fi + + run /bin/bash -c "dokku builder-herokuish:set --global allowed" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_failure + + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:set --global allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:set --global allowed" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure +} + +@test "(builder-herokuish) run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected herokuish" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run $TEST_APP python3 task.py test" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku --quiet run $TEST_APP task" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY=fjdkslafjdk" +} + +@test "(builder-herokuish) cron:run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected herokuish" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_wrapper + echo "output: $output" + echo "status: $status" + assert_success + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'some', 'cron', 'task']" +} + +cron_run_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + mv -f "$APP_REPO_DIR/app-cron.json" "$APP_REPO_DIR/app.json" +} diff --git a/tests/unit/builder-lambda.bats b/tests/unit/builder-lambda.bats new file mode 100644 index 00000000000..9fa0ac841f4 --- /dev/null +++ b/tests/unit/builder-lambda.bats @@ -0,0 +1,262 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + create_app +} + +teardown() { + destroy_app +} + +@test "(builder-lambda:report) info-flag works before deploy" { + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-lambdayml-path" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-lambda:set $TEST_APP lambdayml-path lambda.alt.yml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-lambdayml-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "lambda.alt.yml" + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(builder-lambda:report) --global --builder-lambda-global-lambdayml-path" { + run /bin/bash -c "dokku builder-lambda:set --global lambdayml-path lambda.alt.yml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-lambda:report --global --builder-lambda-global-lambdayml-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "lambda.alt.yml" + + run /bin/bash -c "dokku builder-lambda:set --global lambdayml-path" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(builder-lambda:report) lambdayml-path raw vs computed vs global" { + run /bin/bash -c "dokku builder-lambda:set --global lambdayml-path" + assert_success + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-lambdayml-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-global-lambdayml-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-computed-lambdayml-path" + assert_success + assert_output "lambda.yml" + + run /bin/bash -c "dokku builder-lambda:set --global lambdayml-path lambda.global.yml" + assert_success + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-global-lambdayml-path" + assert_success + assert_output "lambda.global.yml" + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-computed-lambdayml-path" + assert_success + assert_output "lambda.global.yml" + + run /bin/bash -c "dokku builder-lambda:set $TEST_APP lambdayml-path lambda.app.yml" + assert_success + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-lambdayml-path" + assert_success + assert_output "lambda.app.yml" + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-global-lambdayml-path" + assert_success + assert_output "lambda.global.yml" + + run /bin/bash -c "dokku builder-lambda:report $TEST_APP --builder-lambda-computed-lambdayml-path" + assert_success + assert_output "lambda.app.yml" + + run /bin/bash -c "dokku builder-lambda:set $TEST_APP lambdayml-path" + assert_success + + run /bin/bash -c "dokku builder-lambda:set --global lambdayml-path" + assert_success +} + +@test "(builder-lambda:set)" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected lambda" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app lambda-python dokku@$DOKKU_DOMAIN:$TEST_APP inject_lambda_yml + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'Building app with image mlupin/docker-lambda:python3.9-build' + assert_output_contains 'Installing dependencies via pip' + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Hello World" 0 + assert_success + + run /bin/bash -c "curl -d {} --silent --write-out '%{http_code}\n' $(dokku url $TEST_APP)/2015-03-31/functions/function.handler/invocations | grep 200" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Hello World" + assert_success + + run /bin/bash -c "dokku builder-lambda:set $TEST_APP lambdayml-path nonexistent.yml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'Building app with image mlupin/docker-lambda:python3.9-build' + assert_output_contains 'Installing dependencies via pip' + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Hello World" 0 + assert_success + + run /bin/bash -c "curl -d {} --silent --write-out '%{http_code}\n' $(dokku url $TEST_APP)/2015-03-31/functions/function.handler/invocations | grep 200" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Hello World" + assert_success + + run /bin/bash -c "dokku builder-lambda:set $TEST_APP lambdayml-path lambda2.yml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'Building app with image mlupin/docker-lambda:python3.9-build' + assert_output_contains 'Installing dependencies via pip' + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Hello World" 0 + assert_success + + run /bin/bash -c "curl -d {} --silent --write-out '%{http_code}\n' $(dokku url $TEST_APP)/2015-03-31/functions/function.handler/invocations | grep 200" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Hello World" + assert_success + + run /bin/bash -c "dokku builder-lambda:set $TEST_APP lambdayml-path" + echo "output: $output" + echo "status: $status" + assert_success + + # cache will be used + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'Building app with image mlupin/docker-lambda:python3.9-build' + assert_output_contains 'Installing dependencies via pip' 0 + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Hello World" 0 + assert_success + + run /bin/bash -c "curl -d {} --silent --write-out '%{http_code}\n' $(dokku url $TEST_APP)/2015-03-31/functions/function.handler/invocations | grep 200" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Hello World" + assert_success +} + +@test "(builder-lambda) core-post-extract renames the configured lambda.yml" { + echo "---" >"$BATS_TEST_TMPDIR/lambda2.yml" + + run /bin/bash -c "dokku builder-lambda:set $TEST_APP lambdayml-path lambda2.yml" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugin_script builder-lambda core-post-extract "$TEST_APP" "$BATS_TEST_TMPDIR" HEAD + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $BATS_TEST_TMPDIR/lambda.yml" + assert_success + + run /bin/bash -c "test ! -e $BATS_TEST_TMPDIR/lambda2.yml" + assert_success +} + +inject_lambda_yml() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "-----> Injecting lambda.yml" + echo "---" >>"$APP_REPO_DIR/lambda.yml" + + echo "-----> Injecting lambda.yml" + echo "---" >>"$APP_REPO_DIR/lambda.yml" + + echo "-----> Injecting lambda2.yml" + echo "---" >>"$APP_REPO_DIR/lambda2.yml" + echo "build_image: mlupin/docker-lambda:python3.9-build" >>"$APP_REPO_DIR/lambda2.yml" + echo "run_image: mlupin/docker-lambda:python3.9" >>"$APP_REPO_DIR/lambda2.yml" +} diff --git a/tests/unit/builder-nixpacks.bats b/tests/unit/builder-nixpacks.bats new file mode 100644 index 00000000000..d0a1ac1a50a --- /dev/null +++ b/tests/unit/builder-nixpacks.bats @@ -0,0 +1,240 @@ +#!/usr/bin/env bats + +load test_helper + +setup_file() { + install_nixpacks +} + +setup() { + create_app +} + +teardown() { + destroy_app +} + +@test "(builder-nixpacks:report) info-flag works before deploy" { + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-nixpackstoml-path" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-nixpacks:set $TEST_APP nixpackstoml-path nixpacks.alt.toml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-nixpackstoml-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "nixpacks.alt.toml" + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(builder-nixpacks:report) --global --builder-nixpacks-global-nixpackstoml-path" { + run /bin/bash -c "dokku builder-nixpacks:set --global nixpackstoml-path nixpacks.alt.toml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-nixpacks:report --global --builder-nixpacks-global-nixpackstoml-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "nixpacks.alt.toml" + + run /bin/bash -c "dokku builder-nixpacks:set --global nixpackstoml-path" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(builder-nixpacks:report) nixpackstoml-path raw vs computed vs global" { + run /bin/bash -c "dokku builder-nixpacks:set --global nixpackstoml-path" + assert_success + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-nixpackstoml-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-global-nixpackstoml-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-computed-nixpackstoml-path" + assert_success + assert_output "nixpacks.toml" + + run /bin/bash -c "dokku builder-nixpacks:set --global nixpackstoml-path nixpacks.global.toml" + assert_success + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-global-nixpackstoml-path" + assert_success + assert_output "nixpacks.global.toml" + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-computed-nixpackstoml-path" + assert_success + assert_output "nixpacks.global.toml" + + run /bin/bash -c "dokku builder-nixpacks:set $TEST_APP nixpackstoml-path nixpacks.app.toml" + assert_success + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-nixpackstoml-path" + assert_success + assert_output "nixpacks.app.toml" + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-global-nixpackstoml-path" + assert_success + assert_output "nixpacks.global.toml" + + run /bin/bash -c "dokku builder-nixpacks:report $TEST_APP --builder-nixpacks-computed-nixpackstoml-path" + assert_success + assert_output "nixpacks.app.toml" + + run /bin/bash -c "dokku builder-nixpacks:set $TEST_APP nixpackstoml-path" + assert_success + + run /bin/bash -c "dokku builder-nixpacks:set --global nixpackstoml-path" + assert_success +} + +@test "(builder-nixpacks:set)" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected nixpacks" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'load build definition from Dockerfile' -1 + assert_output_contains "SECRET_KEY: fjdkslafjdk" + + run /bin/bash -c "dokku builder-nixpacks:set $TEST_APP nixpackstoml-path nonexistent.toml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'load build definition from Dockerfile' -1 +} + +@test "(builder-nixpacks) run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected nixpacks" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP build '--label=com.dokku.build-test=safe'" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'load build definition from Dockerfile' -1 + assert_output_contains "SECRET_KEY: fjdkslafjdk" + + run /bin/bash -c "dokku run $TEST_APP python3 task.py test" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku --quiet run $TEST_APP task" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY=fjdkslafjdk" +} + +@test "(builder-nixpacks) cron:run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected nixpacks" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_wrapper + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'load build definition from Dockerfile' -1 + assert_output_contains "SECRET_KEY: fjdkslafjdk" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'some', 'cron', 'task']" +} + +@test "(builder-nixpacks) core-post-extract renames the configured nixpacks.toml" { + echo "" >"$BATS_TEST_TMPDIR/nixpacks.alt.toml" + + run /bin/bash -c "dokku builder-nixpacks:set $TEST_APP nixpackstoml-path nixpacks.alt.toml" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugin_script builder-nixpacks core-post-extract "$TEST_APP" "$BATS_TEST_TMPDIR" HEAD + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $BATS_TEST_TMPDIR/nixpacks.toml" + assert_success + + run /bin/bash -c "test ! -e $BATS_TEST_TMPDIR/nixpacks.alt.toml" + assert_success +} + +cron_run_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + add_requirements_txt "$APP" "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cron.json" "$APP_REPO_DIR/app.json" +} diff --git a/tests/unit/builder-pack.bats b/tests/unit/builder-pack.bats index ddc8b966b54..ef8e648493d 100644 --- a/tests/unit/builder-pack.bats +++ b/tests/unit/builder-pack.bats @@ -7,11 +7,104 @@ setup_file() { } setup() { + global_setup create_app } teardown() { destroy_app + global_teardown +} + +@test "(builder-pack:report) info-flag works before deploy" { + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-projecttoml-path" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path project.alt.toml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-projecttoml-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "project.alt.toml" + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(builder-pack:report) --global --builder-pack-global-projecttoml-path" { + run /bin/bash -c "dokku builder-pack:set --global projecttoml-path project.alt.toml" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-pack:report --global --builder-pack-global-projecttoml-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "project.alt.toml" + + run /bin/bash -c "dokku builder-pack:set --global projecttoml-path" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(builder-pack:report) projecttoml-path raw vs computed vs global" { + run /bin/bash -c "dokku builder-pack:set --global projecttoml-path" + assert_success + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-projecttoml-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-global-projecttoml-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-computed-projecttoml-path" + assert_success + assert_output "project.toml" + + run /bin/bash -c "dokku builder-pack:set --global projecttoml-path project.global.toml" + assert_success + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-global-projecttoml-path" + assert_success + assert_output "project.global.toml" + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-computed-projecttoml-path" + assert_success + assert_output "project.global.toml" + + run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path project.app.toml" + assert_success + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-projecttoml-path" + assert_success + assert_output "project.app.toml" + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-global-projecttoml-path" + assert_success + assert_output "project.global.toml" + + run /bin/bash -c "dokku builder-pack:report $TEST_APP --builder-pack-computed-projecttoml-path" + assert_success + assert_output "project.app.toml" + + run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path" + assert_success + + run /bin/bash -c "dokku builder-pack:set --global projecttoml-path" + assert_success } @test "(builder-pack:set)" { @@ -25,18 +118,17 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku buildpacks:set-property $TEST_APP stack heroku/buildpacks:20" + run /bin/bash -c "dokku buildpacks:set-property $TEST_APP stack heroku/builder:24" echo "output: $output" echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP inject_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt_cnb echo "output: $output" echo "status: $status" assert_success assert_output_contains 'Building with buildpack 1' 0 - assert_output_contains 'Installing requirements with pip' - assert_output_contains "Build time env var SECRET_KEY=fjdkslafjdk" + assert_output_contains 'Installing dependencies using pip' run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path nonexistent.toml" echo "output: $output" @@ -48,7 +140,7 @@ teardown() { echo "status: $status" assert_success assert_output_contains 'Building with buildpack 1' 0 - assert_output_contains 'Installing requirements with pip' + assert_output_contains 'Installing dependencies using pip' run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path project2.toml" echo "output: $output" @@ -60,7 +152,7 @@ teardown() { echo "status: $status" assert_success assert_output_contains 'Building with buildpack 1' - assert_output_contains 'Installing requirements with pip' 0 + assert_output_contains 'Installing dependencies using pip' 0 run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path" echo "output: $output" @@ -72,12 +164,186 @@ teardown() { echo "status: $status" assert_success assert_output_contains 'Building with buildpack 1' 0 - assert_output_contains 'Installing requirements with pip' + assert_output_contains 'Installing dependencies using pip' +} + +@test "(builder-pack) run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP build '--label=com.dokku.build-test=safe'" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt_cnb + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'from cnb stack' + assert_output_contains 'Building with buildpack 1' 0 + assert_output_contains 'Installing dependencies using pip' + + run /bin/bash -c "dokku run $TEST_APP python3 task.py test" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku --quiet run $TEST_APP task" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY=fjdkslafjdk" +} + +@test "(builder-pack) git:from-image without a Procfile" { + run /bin/bash -c "dokku git:from-image $TEST_APP dokku/smoke-test-gradle-app:1" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(ps) cnb env vars" { + run /bin/bash -c "dokku config:set $TEST_APP APP_RESPECTS_ENV_VARS=1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt_cnb + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "curl $(dokku url $TEST_APP)/env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains '"APP_RESPECTS_ENV_VARS": "1"' +} + +@test "(builder-pack) cron:run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_wrapper + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'from cnb stack' + assert_output_contains 'Building with buildpack 1' 0 + assert_output_contains 'Installing dependencies using pip' + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'some', 'cron', 'task']" } -inject_requirements_txt() { +@test "(builder-pack) cron:run with Procfile reference" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_procfile_wrapper + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'from cnb stack' + assert_output_contains 'Building with buildpack 1' 0 + assert_output_contains 'Installing dependencies using pip' + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku --quiet cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" +} + +@test "(builder-pack) core-post-extract renames the configured project.toml" { + echo "" >"$BATS_TEST_TMPDIR/project2.toml" + + run /bin/bash -c "dokku builder-pack:set $TEST_APP projecttoml-path project2.toml" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugin_script builder-pack core-post-extract "$TEST_APP" "$BATS_TEST_TMPDIR" HEAD + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $BATS_TEST_TMPDIR/project.toml" + assert_success + + run /bin/bash -c "test ! -e $BATS_TEST_TMPDIR/project2.toml" + assert_success +} + +cron_run_wrapper() { local APP="$1" local APP_REPO_DIR="$2" [[ -z "$APP" ]] && local APP="$TEST_APP" - echo "flask" >>"$APP_REPO_DIR/requirements.txt" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + add_requirements_txt "$APP" "$APP_REPO_DIR" + ls -lah "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cnb-cron.json" "$APP_REPO_DIR/app.json" +} + +cron_run_procfile_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + add_requirements_txt "$APP" "$APP_REPO_DIR" + ls -lah "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cnb-cron-procfile.json" "$APP_REPO_DIR/app.json" } diff --git a/tests/unit/builder-railpack.bats b/tests/unit/builder-railpack.bats new file mode 100644 index 00000000000..d94cba63b9f --- /dev/null +++ b/tests/unit/builder-railpack.bats @@ -0,0 +1,251 @@ +#!/usr/bin/env bats + +load test_helper + +setup_file() { + global_setup + install_railpack + touch /etc/default/dokku + sudo tee -a /etc/default/dokku <<<"export BUILDKIT_HOST='docker-container://buildkit'" +} + +teardown_file() { + docker container stop buildkit || true + docker container rm buildkit || true +} + +setup() { + global_setup + create_app + docker run --rm --privileged -d --name buildkit moby/buildkit:latest +} + +teardown() { + destroy_app + global_teardown +} + +@test "(builder-railpack:report) info-flag works before deploy" { + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-railpackjson-path" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-railpack:set $TEST_APP railpackjson-path railpack.alt.json" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-railpackjson-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "railpack.alt.json" + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(builder-railpack:report) --global --builder-railpack-global-railpackjson-path" { + run /bin/bash -c "dokku builder-railpack:set --global railpackjson-path railpack.alt.json" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder-railpack:report --global --builder-railpack-global-railpackjson-path" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "railpack.alt.json" + + run /bin/bash -c "dokku builder-railpack:set --global railpackjson-path" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(builder-railpack:report) railpackjson-path raw vs computed vs global" { + run /bin/bash -c "dokku builder-railpack:set --global railpackjson-path" + assert_success + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-railpackjson-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-global-railpackjson-path" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-computed-railpackjson-path" + assert_success + assert_output "railpack.json" + + run /bin/bash -c "dokku builder-railpack:set --global railpackjson-path railpack.global.json" + assert_success + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-global-railpackjson-path" + assert_success + assert_output "railpack.global.json" + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-computed-railpackjson-path" + assert_success + assert_output "railpack.global.json" + + run /bin/bash -c "dokku builder-railpack:set $TEST_APP railpackjson-path railpack.app.json" + assert_success + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-railpackjson-path" + assert_success + assert_output "railpack.app.json" + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-global-railpackjson-path" + assert_success + assert_output "railpack.global.json" + + run /bin/bash -c "dokku builder-railpack:report $TEST_APP --builder-railpack-computed-railpackjson-path" + assert_success + assert_output "railpack.app.json" + + run /bin/bash -c "dokku builder-railpack:set $TEST_APP railpackjson-path" + assert_success + + run /bin/bash -c "dokku builder-railpack:set --global railpackjson-path" + assert_success +} + +@test "(builder-railpack:set)" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected railpack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'Successfully built image in' + assert_output_contains "SECRET_KEY: fjdkslafjdk" + + run /bin/bash -c "dokku builder-railpack:set $TEST_APP railpackjson-path nonexistent.json" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'Successfully built image in' +} + +@test "(builder-pack) run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected railpack" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP build '--label=com.dokku.build-test=safe'" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'create mise config' + assert_output_contains 'Successfully built image in' + + run /bin/bash -c "dokku run $TEST_APP python3 task.py test" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku --quiet run $TEST_APP task" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'test']" + + run /bin/bash -c "dokku run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY=fjdkslafjdk" +} + +@test "(builder-railpack) cron:run" { + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected railpack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_wrapper + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'create mise config' + assert_output_contains 'Successfully built image in' + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'some', 'cron', 'task']" +} + +@test "(builder-railpack) core-post-extract renames the configured railpack.json" { + echo "{}" >"$BATS_TEST_TMPDIR/railpack.alt.json" + + run /bin/bash -c "dokku builder-railpack:set $TEST_APP railpackjson-path railpack.alt.json" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugin_script builder-railpack core-post-extract "$TEST_APP" "$BATS_TEST_TMPDIR" HEAD + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $BATS_TEST_TMPDIR/railpack.json" + assert_success + + run /bin/bash -c "test ! -e $BATS_TEST_TMPDIR/railpack.alt.json" + assert_success +} + +cron_run_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + add_requirements_txt "$APP" "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cron.json" "$APP_REPO_DIR/app.json" +} diff --git a/tests/unit/builder.bats b/tests/unit/builder.bats index 9caf52f3c0d..8681f05e3ff 100644 --- a/tests/unit/builder.bats +++ b/tests/unit/builder.bats @@ -7,11 +7,12 @@ setup() { } teardown() { + dokku builder:set --global skip-cleanup >/dev/null 2>/dev/null || true destroy_app } @test "(builder) builder-detect [set]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM # test project.toml @@ -79,7 +80,7 @@ teardown() { } @test "(builder) builder-detect [pack]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM # test project.toml @@ -99,23 +100,10 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - - # test DOKKU_CNB_EXPERIMENTAL env var - run /bin/bash -c "dokku config:set $TEST_APP DOKKU_CNB_EXPERIMENTAL=1" - echo "output: $output" - echo "status: $status" - assert_success - - chown -R dokku:dokku "$TMP" - run /bin/bash -c "dokku plugin:trigger builder-detect $TEST_APP $TMP" - echo "output: $output" - echo "status: $status" - assert_success - assert_line 0 "pack" } @test "(builder) builder-detect [dockerfile]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM run touch "$TMP/Dockerfile" @@ -132,7 +120,7 @@ teardown() { } @test "(builder) builder-detect [herokuish]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM touch "$TMP/Dockerfile" @@ -188,6 +176,205 @@ teardown() { assert_line 0 "herokuish" } +@test "(builder:report) --global --format json" { + run /bin/bash -c "dokku builder:set --global selected dockerfile" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:report --global --format json | jq -r '.\"builder-global-selected\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dockerfile" + + run /bin/bash -c "dokku builder:report --global --format json | jq -r '.\"global-selected\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dockerfile" + + run /bin/bash -c "dokku builder:report --global --format json | jq -r 'has(\"builder-selected\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder:report --global --format json | jq -r 'has(\"selected\") and has(\"builder-global-selected\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder:report --global --format json | jq -r 'has(\"computed-skip-cleanup\") and has(\"builder-computed-skip-cleanup\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builder:report --global" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set --global selected" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(builder:report) selected raw vs computed vs global" { + run /bin/bash -c "dokku builder:set --global selected" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-selected" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-selected" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-selected" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder:set --global selected dockerfile" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-selected" + assert_success + assert_output "dockerfile" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-selected" + assert_success + assert_output "dockerfile" + + run /bin/bash -c "dokku builder:set $TEST_APP selected herokuish" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-selected" + assert_success + assert_output "herokuish" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-selected" + assert_success + assert_output "dockerfile" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-selected" + assert_success + assert_output "herokuish" + + run /bin/bash -c "dokku builder:set $TEST_APP selected" + assert_success + + run /bin/bash -c "dokku builder:set --global selected" + assert_success +} + +@test "(builder:report) build-dir raw vs computed vs global" { + run /bin/bash -c "dokku builder:set --global build-dir" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-build-dir" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-build-dir" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-build-dir" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder:set --global build-dir global/subdir" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-build-dir" + assert_success + assert_output "global/subdir" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-build-dir" + assert_success + assert_output "global/subdir" + + run /bin/bash -c "dokku builder:set $TEST_APP build-dir app/subdir" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-build-dir" + assert_success + assert_output "app/subdir" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-build-dir" + assert_success + assert_output "global/subdir" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-build-dir" + assert_success + assert_output "app/subdir" + + run /bin/bash -c "dokku builder:set $TEST_APP build-dir" + assert_success + + run /bin/bash -c "dokku builder:set --global build-dir" + assert_success +} + +@test "(builder:report) skip-cleanup raw vs computed vs global" { + run /bin/bash -c "dokku builder:set --global skip-cleanup" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-skip-cleanup" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-skip-cleanup" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-skip-cleanup" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder:set --global skip-cleanup true" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-skip-cleanup" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-skip-cleanup" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builder:set $TEST_APP skip-cleanup false" + assert_success + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-skip-cleanup" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-global-skip-cleanup" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builder:report $TEST_APP --builder-computed-skip-cleanup" + assert_success + assert_output "false" + + run /bin/bash -c "dokku builder:set $TEST_APP skip-cleanup" + assert_success + + run /bin/bash -c "dokku builder:set --global skip-cleanup" + assert_success +} + @test "(builder:set)" { run deploy_app python echo "output: $output" diff --git a/tests/unit/buildpacks.bats b/tests/unit/buildpacks.bats index 3382a8a8843..e52e700fbb9 100644 --- a/tests/unit/buildpacks.bats +++ b/tests/unit/buildpacks.bats @@ -184,6 +184,75 @@ teardown() { assert_output "https://github.com/heroku/heroku-buildpack-golang.git https://github.com/heroku/heroku-buildpack-python.git https://github.com/heroku/heroku-buildpack-php.git" } +@test "(buildpacks) buildpacks:set --replace - failure" { + run /bin/bash -c "dokku buildpacks:set --replace $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "buildpacks:clear" + + run /bin/bash -c "dokku buildpacks:set --replace --index 1 $TEST_APP heroku/nodejs" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "--index" + + run /bin/bash -c "dokku buildpacks:set $TEST_APP heroku/nodejs heroku/ruby" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "--replace" +} + +@test "(buildpacks) buildpacks:set --replace - atomicity" { + run /bin/bash -c "dokku buildpacks:add $TEST_APP heroku/nodejs" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku buildpacks:set --replace $TEST_APP heroku/ruby nodejs" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs" + echo "output: $output" + echo "status: $status" + assert_output "https://github.com/heroku/heroku-buildpack-nodejs.git" +} + +@test "(buildpacks) buildpacks:set --replace - success" { + run /bin/bash -c "dokku buildpacks:add $TEST_APP heroku/nodejs" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku buildpacks:add $TEST_APP heroku/python" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku buildpacks:set --replace $TEST_APP heroku/ruby heroku/golang heroku/php" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs" + echo "output: $output" + echo "status: $status" + assert_output "https://github.com/heroku/heroku-buildpack-ruby.git https://github.com/heroku/heroku-buildpack-golang.git https://github.com/heroku/heroku-buildpack-php.git" + + run /bin/bash -c "dokku buildpacks:set --replace $TEST_APP heroku/nodejs" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs" + echo "output: $output" + echo "status: $status" + assert_output "https://github.com/heroku/heroku-buildpack-nodejs.git" +} + @test "(buildpacks) buildpacks:set-property" { run /bin/bash -c "dokku buildpacks:set-property --global stack gliderlabs/herokuish:latest" echo "output: $output" @@ -191,6 +260,69 @@ teardown() { assert_success } +@test "(buildpacks:report) stack raw vs computed vs global" { + run /bin/bash -c "dokku buildpacks:set-property --global stack" + assert_success + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r '.\"buildpacks-stack\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r '.\"buildpacks-global-stack\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku buildpacks:set-property --global stack gliderlabs/herokuish:global" + assert_success + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r '.\"buildpacks-global-stack\"'" + assert_success + assert_output "gliderlabs/herokuish:global" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r '.\"buildpacks-computed-stack\"'" + assert_success + assert_output "gliderlabs/herokuish:global" + + run /bin/bash -c "dokku buildpacks:set-property $TEST_APP stack gliderlabs/herokuish:app" + assert_success + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r '.\"buildpacks-stack\"'" + assert_success + assert_output "gliderlabs/herokuish:app" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r '.\"buildpacks-global-stack\"'" + assert_success + assert_output "gliderlabs/herokuish:global" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r '.\"buildpacks-computed-stack\"'" + assert_success + assert_output "gliderlabs/herokuish:app" + + run /bin/bash -c "dokku buildpacks:set-property $TEST_APP stack" + assert_success + + run /bin/bash -c "dokku buildpacks:set-property --global stack" + assert_success +} + +@test "(buildpacks:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r 'has(\"stack\") and has(\"buildpacks-stack\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r 'has(\"global-stack\") and has(\"buildpacks-global-stack\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r 'has(\"computed-stack\") and has(\"buildpacks-computed-stack\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku buildpacks:report $TEST_APP --format json | jq -r 'has(\"list\") and has(\"buildpacks-list\")'" + assert_success + assert_output "true" +} + @test "(buildpacks) buildpacks:remove" { run /bin/bash -c "dokku buildpacks:set $TEST_APP heroku/nodejs" echo "output: $output" @@ -205,7 +337,7 @@ teardown() { run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs" echo "output: $output" echo "status: $status" - assert_output_contains "https://github.com/heroku/heroku-buildpack-nodejs.git https://github.com/heroku/heroku-buildpack-ruby.git" + assert_output "https://github.com/heroku/heroku-buildpack-nodejs.git https://github.com/heroku/heroku-buildpack-ruby.git" run /bin/bash -c "dokku buildpacks:remove $TEST_APP heroku/nodejs" echo "output: $output" @@ -215,7 +347,7 @@ teardown() { run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs" echo "output: $output" echo "status: $status" - assert_output_contains "https://github.com/heroku/heroku-buildpack-ruby.git" + assert_output "https://github.com/heroku/heroku-buildpack-ruby.git" run /bin/bash -c "dokku buildpacks:remove $TEST_APP heroku/php" echo "output: $output" @@ -231,8 +363,37 @@ teardown() { echo "output: $output" echo "status: $status" assert_output_not_exists -} + run /bin/bash -c "dokku buildpacks:add $TEST_APP https://github.com/heroku/heroku-buildpack-nodejs.git" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku buildpacks:add $TEST_APP https://github.com/heroku/heroku-buildpack-ruby.git" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku buildpacks:add $TEST_APP https://github.com/yespark/heroku-imagemagick-buildpack" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs" + echo "output: $output" + echo "status: $status" + assert_output "https://github.com/heroku/heroku-buildpack-nodejs.git https://github.com/heroku/heroku-buildpack-ruby.git https://github.com/yespark/heroku-imagemagick-buildpack" + + run /bin/bash -c "dokku buildpacks:remove $TEST_APP --index 1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs" + echo "output: $output" + echo "status: $status" + assert_output "https://github.com/heroku/heroku-buildpack-ruby.git https://github.com/yespark/heroku-imagemagick-buildpack" +} @test "(buildpacks) buildpacks:clear" { run /bin/bash -c "dokku buildpacks:set $TEST_APP heroku/nodejs" @@ -299,7 +460,7 @@ teardown() { } @test "(buildpacks) cleanup existing .buildpacks file" { - run deploy_app python dokku@dokku.me:$TEST_APP template_buildpacks_cleanup + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_buildpacks_cleanup echo "output: $output" echo "status: $status" assert_success @@ -307,6 +468,33 @@ teardown() { assert_output_contains "heroku-buildpack-python" } +@test "(buildpacks) deploy with buildpacks from app.json" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_buildpacks_appjson + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "heroku-buildpack-python" +} + +template_buildpacks_appjson() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "injecting app.json with buildpacks -> $APP_REPO_DIR/app.json" + cat <"$APP_REPO_DIR/app.json" +{ + "buildpacks": [ + { + "url": "heroku/python" + } + ] +} +EOF + + echo "injecting requirements.txt -> $APP_REPO_DIR/requirements.txt" + touch "$APP_REPO_DIR/requirements.txt" +} + template_buildpacks_cleanup() { local APP="$1" local APP_REPO_DIR="$2" diff --git a/tests/unit/builds.bats b/tests/unit/builds.bats new file mode 100644 index 00000000000..11591688472 --- /dev/null +++ b/tests/unit/builds.bats @@ -0,0 +1,352 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + rm -rf "$DOKKU_LIB_ROOT/data/builds/$TEST_APP" || true + destroy_app + global_teardown +} + +write_finished_record() { + local app="$1" id="$2" status="$3" source="${4:-git-hook}" pid="${5:-99999}" kind="${6:-build}" exit_code="${7:-0}" + local dir="$DOKKU_LIB_ROOT/data/builds/$app" + mkdir -p "$dir" + local now + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + cat >"$dir/$id.json" <"$dir/$id.log" + chown -R dokku:dokku "$DOKKU_LIB_ROOT/data/builds" +} + +write_running_record() { + local app="$1" id="$2" pid="$3" source="${4:-git-hook}" kind="${5:-build}" + local dir="$DOKKU_LIB_ROOT/data/builds/$app" + mkdir -p "$dir" + local now + now="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + cat >"$dir/$id.json" <"$dir/$id.log" + chown -R dokku:dokku "$DOKKU_LIB_ROOT/data/builds" +} + +@test "(builds) builds:help" { + run /bin/bash -c "dokku builds" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage running and historical builds" + + run /bin/bash -c "dokku builds:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage running and historical builds" +} + +@test "(builds:list) returns empty list when no builds have run" { + run /bin/bash -c "dokku builds:list $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "No builds recorded" +} + +@test "(builds:list) shows a finished record after a successful deploy" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builds:list $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "succeeded" + assert_output_contains "git-hook" +} + +@test "(builds:list) shows an abandoned record when the PID is gone but the record is still running" { + write_running_record "$TEST_APP" "ghost001" 99999 "git-hook" + + run /bin/bash -c "dokku builds:list $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "abandoned" +} + +@test "(builds:list --kind=build) filters to build-kind records" { + write_finished_record "$TEST_APP" "b1" "succeeded" "git-hook" 1001 "build" + write_finished_record "$TEST_APP" "d1" "succeeded" "ps:restart" 1002 "deploy" + + run /bin/bash -c "dokku builds:list $TEST_APP --kind build" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "b1" + assert_output_not_contains "d1" +} + +@test "(builds:list --kind=deploy) filters to deploy-kind records" { + write_finished_record "$TEST_APP" "b1" "succeeded" "git-hook" 1001 "build" + write_finished_record "$TEST_APP" "d1" "succeeded" "ps:restart" 1002 "deploy" + + run /bin/bash -c "dokku builds:list $TEST_APP --kind deploy" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "d1" + assert_output_not_contains "b1" +} + +@test "(builds:list --kind=invalid) fails with a usage error" { + run /bin/bash -c "dokku builds:list $TEST_APP --kind=garbage" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid --kind" +} + +@test "(builds:set) writes a per-app retention" { + run /bin/bash -c "dokku builds:set $TEST_APP retention 5" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builds:report $TEST_APP --builds-retention" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "5" +} + +@test "(builds:set --global) writes the global retention" { + run /bin/bash -c "dokku builds:set --global retention 7" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builds:report $TEST_APP --builds-computed-retention" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "7" + + dokku builds:set --global retention "" || true +} + +@test "(builds:set) clears the override when called with no value" { + dokku builds:set "$TEST_APP" retention 5 + + run /bin/bash -c "dokku builds:set $TEST_APP retention" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builds:report $TEST_APP --builds-retention" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" +} + +@test "(builds:set) rejects non-positive-integer values" { + run /bin/bash -c "dokku builds:set $TEST_APP retention 0" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku builds:set $TEST_APP retention abc" + echo "output: $output" + echo "status: $status" + assert_failure +} + +@test "(builds:info) returns non-zero for a missing build-id" { + run /bin/bash -c "dokku builds:info $TEST_APP nonexistent" + echo "output: $output" + echo "status: $status" + assert_failure +} + +@test "(builds:info) returns the recorded fields including --format json" { + write_finished_record "$TEST_APP" "info001" "succeeded" "git-hook" 1234 "build" + + run /bin/bash -c "dokku builds:info $TEST_APP info001" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "info001" -1 + assert_output_contains "succeeded" + assert_output_contains "Log:" + + run /bin/bash -c "dokku builds:info $TEST_APP info001 --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "\"id\":\"info001\"" + assert_output_contains "\"log_path\":" +} + +@test "(builds:cancel) returns 0 with a friendly message when no lock is present" { + run /bin/bash -c "dokku builds:cancel $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "App not currently deploying" +} + +@test "(builds:cancel) refuses to cancel a record whose status is not running" { + write_finished_record "$TEST_APP" "done01" "succeeded" "git-hook" 1234 "build" + mkdir -p "$DOKKU_LIB_ROOT/data/apps/$TEST_APP" + echo "done01" >"$DOKKU_LIB_ROOT/data/apps/$TEST_APP/.deploy.lock" + chown dokku:dokku "$DOKKU_LIB_ROOT/data/apps/$TEST_APP/.deploy.lock" + + run /bin/bash -c "dokku builds:cancel $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "no longer running" + + rm -f "$DOKKU_LIB_ROOT/data/apps/$TEST_APP/.deploy.lock" +} + +@test "(builds:cancel) marks an abandoned record as failed instead of canceled" { + write_running_record "$TEST_APP" "abnd01" 99999 "git-hook" + mkdir -p "$DOKKU_LIB_ROOT/data/apps/$TEST_APP" + echo "abnd01" >"$DOKKU_LIB_ROOT/data/apps/$TEST_APP/.deploy.lock" + chown dokku:dokku "$DOKKU_LIB_ROOT/data/apps/$TEST_APP/.deploy.lock" + + run /bin/bash -c "dokku builds:cancel $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "already terminated" + + run /bin/bash -c "dokku builds:info $TEST_APP abnd01" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "failed" +} + +@test "(builds:output) cats the log file for a finished build" { + write_finished_record "$TEST_APP" "out001" "succeeded" "git-hook" 1234 "build" + echo "build log line" >"$DOKKU_LIB_ROOT/data/builds/$TEST_APP/out001.log" + chown dokku:dokku "$DOKKU_LIB_ROOT/data/builds/$TEST_APP/out001.log" + + run /bin/bash -c "dokku builds:output $TEST_APP out001" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "build log line" +} + +@test "(builds:prune) prunes an app's records to retention" { + dokku builds:set "$TEST_APP" retention 2 + + for i in 1 2 3 4 5; do + write_finished_record "$TEST_APP" "p$i" "succeeded" "git-hook" $((1000 + i)) "build" + sleep 1 + done + + run /bin/bash -c "dokku builds:prune $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + count="$(ls $DOKKU_LIB_ROOT/data/builds/$TEST_APP/*.json 2>/dev/null | wc -l)" + echo "remaining: $count" + [[ "$count" -le 2 ]] || (echo "expected <=2 records, got $count" && false) +} + +@test "(builds:prune) reaps an abandoned record and finalizes it as failed" { + write_running_record "$TEST_APP" "ghost02" 99999 "git-hook" + + run /bin/bash -c "dokku builds:prune $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builds:info $TEST_APP ghost02" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "failed" +} + +@test "(builds:prune) skips live-running records" { + local pid=$$ + write_running_record "$TEST_APP" "alive02" "$pid" "git-hook" + + run /bin/bash -c "dokku builds:prune $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builds:info $TEST_APP alive02" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "running" +} + +@test "(builds) [storage] directory is removed on apps:destroy" { + write_finished_record "$TEST_APP" "del01" "succeeded" "git-hook" 1234 "build" + [[ -d "$DOKKU_LIB_ROOT/data/builds/$TEST_APP" ]] + + run /bin/bash -c "dokku --force apps:destroy $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + [[ ! -d "$DOKKU_LIB_ROOT/data/builds/$TEST_APP" ]] + + # Recreate so teardown can run cleanly. + create_app +} + +@test "(builds:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku builds:report $TEST_APP --format json | jq -r 'has(\"retention\") and has(\"builds-retention\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builds:report $TEST_APP --format json | jq -r 'has(\"global-retention\") and has(\"builds-global-retention\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku builds:report $TEST_APP --format json | jq -r 'has(\"computed-retention\") and has(\"builds-computed-retention\")'" + assert_success + assert_output "true" + + # build-* status keys have no plugin-prefix collision, so they pass through unchanged + run /bin/bash -c "dokku builds:report $TEST_APP --format json | jq -r 'has(\"build-id\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/caddy-vhosts.bats b/tests/unit/caddy-vhosts.bats new file mode 100644 index 00000000000..1c3180359a1 --- /dev/null +++ b/tests/unit/caddy-vhosts.bats @@ -0,0 +1,84 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + create_app +} + +teardown() { + destroy_app + dokku caddy:set --global log-level >/dev/null 2>&1 || true + dokku caddy:set --global tls-internal >/dev/null 2>&1 || true +} + +@test "(caddy:report) info-flag works before deploy" { + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(caddy:report) --format json" { + run /bin/bash -c "dokku caddy:set --global log-level" + assert_success + + run /bin/bash -c "dokku caddy:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:report --global --format json | jq -r '.\"computed-log-level\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" + + run /bin/bash -c "dokku caddy:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global caddy information" +} + +@test "(caddy:report) tls-internal raw global computed" { + run /bin/bash -c "dokku caddy:set --global tls-internal" + assert_success + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + assert_success + assert_output "false" + + run /bin/bash -c "dokku caddy:set --global tls-internal true" + assert_success + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-global-tls-internal" + assert_success + assert_output "true" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + assert_success + assert_output "true" + + run /bin/bash -c "dokku caddy:set $TEST_APP tls-internal false" + assert_success + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal" + assert_success + assert_output "false" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + assert_success + assert_output "false" +} diff --git a/tests/unit/caddy.bats b/tests/unit/caddy.bats new file mode 100644 index 00000000000..dd1e48658f1 --- /dev/null +++ b/tests/unit/caddy.bats @@ -0,0 +1,431 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku nginx:stop + dokku caddy:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory + dokku caddy:set --global letsencrypt-email + dokku caddy:start + create_app +} + +teardown() { + global_teardown + destroy_app + dokku caddy:stop + dokku nginx:start +} + +@test "(caddy:report) --global global vs computed log-level" { + run /bin/bash -c "dokku caddy:report --global --caddy-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:report --global --caddy-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" + + run /bin/bash -c "dokku caddy:set --global log-level DEBUG" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:report --global --caddy-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "DEBUG" + + run /bin/bash -c "dokku caddy:report --global --caddy-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "DEBUG" + + run /bin/bash -c "dokku caddy:set --global log-level" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:report --global --caddy-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:report --global --caddy-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" +} + +@test "(caddy:report) --global raw and computed keys in --format json" { + run /bin/bash -c "dokku caddy:report --global --format json | jq -r '.\"global-image\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:report --global --format json | jq -r '.\"computed-image\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku caddy:report --global --format json | jq -r '.\"global-polling-interval\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:report --global --format json | jq -r '.\"computed-polling-interval\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5s" +} + +@test "(caddy) global-only keys" { + for key in image letsencrypt-email letsencrypt-server log-level polling-interval; do + run /bin/bash -c "dokku caddy:set $TEST_APP $key somevalue" + echo "key: $key" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" + done + + run /bin/bash -c "dokku caddy:set $TEST_APP tls-internal true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku caddy:set $TEST_APP tls-internal" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(caddy:report) tls-internal raw vs computed vs global" { + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-global-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:set $TEST_APP tls-internal true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku caddy:set $TEST_APP tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" +} + +@test "(caddy:set) --global tls-internal" { + run /bin/bash -c "dokku caddy:set --global tls-internal true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:report --global --caddy-global-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-global-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku caddy:set $TEST_APP tls-internal false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku caddy:report $TEST_APP --caddy-computed-tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku caddy:set $TEST_APP tls-internal" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:set --global tls-internal" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(caddy) caddy:help" { + run /bin/bash -c "dokku caddy" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the caddy proxy integration" + help_output="$output" + + run /bin/bash -c "dokku caddy:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the caddy proxy integration" + assert_output "$help_output" +} + +@test "(caddy) single domain" { + run /bin/bash -c "dokku proxy:set $TEST_APP caddy" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "curl --silent $(dokku url $TEST_APP)" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "python/http.server" +} + +@test "(caddy) multiple domains" { + run /bin/bash -c "dokku proxy:set $TEST_APP caddy" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "curl --silent $TEST_APP.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "python/http.server" + + run /bin/bash -c "curl --silent $TEST_APP-2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "python/http.server" +} + +@test "(caddy) ssl" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP caddy" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"caddy\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP.${DOKKU_DOMAIN}:80" + + run /bin/bash -c "dokku caddy:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"caddy\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" +} + +@test "(caddy) label management" { + run /bin/bash -c "dokku proxy:set $TEST_APP caddy" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:labels:add $TEST_APP caddy.directive value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:labels:show $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "caddy.directive=value" + + run /bin/bash -c "dokku caddy:labels:show $TEST_APP caddy.directive" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "value" + + run /bin/bash -c "dokku caddy:labels:show $TEST_APP caddy.directive2" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"caddy.directive\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "value" + + run /bin/bash -c "dokku caddy:labels:remove $TEST_APP caddy.directive" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku caddy:labels:show $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "caddy.directive=value" + + run /bin/bash -c "dokku caddy:labels:show $TEST_APP caddy.directive" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"caddy.directive\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} diff --git a/tests/unit/certs.bats b/tests/unit/certs.bats index 2664d73dd57..eb9d7480285 100644 --- a/tests/unit/certs.bats +++ b/tests/unit/certs.bats @@ -41,6 +41,141 @@ teardown() { assert_output "$help_output" } +@test "(certs:report) info-flag works before deploy" { + run /bin/bash -c "dokku certs:report $TEST_APP --ssl-hostnames" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:report $TEST_APP --ssl-dir" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "/$TEST_APP/tls" + + run /bin/bash -c "dokku certs:report $TEST_APP --ssl-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(certs:report) --global --format json" { + run /bin/bash -c "dokku certs:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + assert_output "{}" + + run /bin/bash -c "dokku certs:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global ssl information" + + run /bin/bash -c "dokku certs:report --global --ssl-bogus" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(certs:report) reports hostnames and subject for a CN-only certificate" { + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:report $TEST_APP --ssl-hostnames" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku.me" + + run /bin/bash -c "dokku certs:report $TEST_APP --ssl-subject" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "CN=dokku.me" +} + +@test "(certs:report) preserves multi-field subject order" { + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/domain.com.crt $BATS_TMPDIR/tls/domain.com.key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:report $TEST_APP --ssl-subject" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "C=US; ST=California; L=San Francisco; O=Expa; OU=Operations; CN=node-js-app.dokku.me" + + run /bin/bash -c "dokku certs:report $TEST_APP --ssl-hostnames" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "node-js-app.dokku.me" +} + +@test "(certs:report) reports the CN and all SANs as hostnames" { + local SANS_TLS="$BATS_TMPDIR/tls-sans" + mkdir -p "$SANS_TLS" + tar xf "$BATS_TEST_DIRNAME/server_ssl_sans.tar" -C "$SANS_TLS" + sudo chown -R dokku:dokku "$SANS_TLS" + + run /bin/bash -c "dokku certs:add $TEST_APP $SANS_TLS/server.crt $SANS_TLS/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:report $TEST_APP --ssl-hostnames" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "test.dokku.me www.test.app.dokku.me www.test.dokku.me" + + rm -rf "$SANS_TLS" +} + +@test "(certs) get_ssl_hostnames parses a CN-only certificate" { + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + source "$PLUGIN_CORE_AVAILABLE_PATH/certs/functions" + run get_ssl_hostnames "$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku.me" +} + +@test "(certs) get_ssl_hostnames includes the CN and SANs" { + local SANS_TLS="$BATS_TMPDIR/tls-sans" + mkdir -p "$SANS_TLS" + tar xf "$BATS_TEST_DIRNAME/server_ssl_sans.tar" -C "$SANS_TLS" + sudo chown -R dokku:dokku "$SANS_TLS" + + run /bin/bash -c "dokku certs:add $TEST_APP $SANS_TLS/server.crt $SANS_TLS/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + source "$PLUGIN_CORE_AVAILABLE_PATH/certs/functions" + run get_ssl_hostnames "$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_line "test.dokku.me" + assert_line "www.test.dokku.me" + assert_line "www.test.app.dokku.me" + assert_line_count 3 + + rm -rf "$SANS_TLS" +} + @test "(certs) certs:add" { run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" echo "output: $output" @@ -48,6 +183,40 @@ teardown() { assert_success } +@test "(certs:add) preserves explicit https:443 port mapping" { + run /bin/bash -c "dokku ports:set $TEST_APP http:80:80 https:443:443" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:80:80 https:443:443" +} + +@test "(certs:add) adds default https:443 mapping when none exists" { + run /bin/bash -c "dokku ports:set $TEST_APP http:80:5000" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" +} + @test "(certs) certs:add with multiple dots in the filename" { run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/domain.com.crt $BATS_TMPDIR/tls/domain.com.key" echo "output: $output" @@ -92,6 +261,44 @@ teardown() { assert_success } +@test "(certs) certs-set plugin trigger" { + run_plugn_trigger certs-set "$TEST_APP" "$BATS_TMPDIR/tls/server.crt" "$BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + [[ -f "$DOKKU_ROOT/$TEST_APP/tls/server.crt" ]] + [[ -f "$DOKKU_ROOT/$TEST_APP/tls/server.key" ]] +} + +@test "(certs) certs-set plugin trigger missing key file" { + run_plugn_trigger certs-set "$TEST_APP" "$BATS_TMPDIR/tls/server.crt" "/nonexistent/server.key" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "KEY file specified not found" +} + +@test "(certs) certs-remove plugin trigger" { + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugn_trigger certs-remove "$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + [[ ! -d "$DOKKU_ROOT/$TEST_APP/tls" ]] +} + +@test "(certs) certs-remove plugin trigger without endpoint" { + run_plugn_trigger certs-remove "$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "An app-specific SSL endpoint is not defined" +} + @test "(certs) certs:show" { run /bin/bash -c "dokku certs:show fake-app-name 2>&1" echo "output: $output" @@ -114,6 +321,6 @@ teardown() { run /bin/bash -c "dokku certs:add $TEST_APP < $BATS_TEST_DIRNAME/server_ssl.tar && dokku certs:show $TEST_APP key" echo "output: $output" echo "status: $status" - assert_output_contains "-----END RSA PRIVATE KEY-----" + assert_output_contains "-----END PRIVATE KEY-----" assert_success } diff --git a/tests/unit/checks.bats b/tests/unit/checks.bats index e337dad5640..365702002b6 100644 --- a/tests/unit/checks.bats +++ b/tests/unit/checks.bats @@ -16,6 +16,94 @@ teardown() { global_teardown } +@test "(checks:report) info-flag works before deploy" { + run /bin/bash -c "dokku checks:report $TEST_APP --checks-wait-to-retire" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:set $TEST_APP wait-to-retire 30" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:report $TEST_APP --checks-wait-to-retire" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "30" + + run /bin/bash -c "dokku checks:report $TEST_APP --checks-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(checks:report) --global --format json" { + run /bin/bash -c "dokku checks:set --global wait-to-retire" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:report --global --format json | jq -r '.\"global-wait-to-retire\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku checks:report --global --format json | jq -r '.\"computed-wait-to-retire\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "60" + + run /bin/bash -c "dokku checks:set --global wait-to-retire 90" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:report --global --format json | jq -r '.\"global-wait-to-retire\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "90" + + run /bin/bash -c "dokku checks:report --global --format json | jq -r '.\"computed-wait-to-retire\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "90" + + run /bin/bash -c "dokku checks:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global checks information" + + run /bin/bash -c "dokku checks:set --global wait-to-retire" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:report --global --checks-global-wait-to-retire" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku checks:report --global --checks-computed-wait-to-retire" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "60" +} + @test "(checks) checks:help" { run /bin/bash -c "dokku checks" echo "output: $output" @@ -36,7 +124,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" assert_output "_all_" @@ -48,30 +136,30 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" assert_output "_all_" - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" - assert_output "" + assert_output "none" run /bin/bash -c "dokku checks:enable $TEST_APP" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" - assert_output "" + assert_output "none" - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" - assert_output "" + assert_output "none" } @test "(checks) checks:disable -> checks:skip" { @@ -80,27 +168,27 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" assert_output "web,worker,urgentworker,notifications" - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" - assert_output "" + assert_output "none" run /bin/bash -c "dokku checks:skip $TEST_APP urgentworker,worker" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" assert_output "urgentworker,worker" - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" assert_output "web,notifications" @@ -112,7 +200,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" assert_output "_all_" @@ -124,30 +212,30 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" assert_output "_all_" - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" - assert_output "" + assert_output "none" run /bin/bash -c "dokku checks:enable $TEST_APP" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" - assert_output "" + assert_output "none" - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" - assert_output "" + assert_output "none" } @test "(checks) checks:skip -> checks:disable" { @@ -156,27 +244,27 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" assert_output "web,worker,urgentworker,notifications" - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" - assert_output "" + assert_output "none" run /bin/bash -c "dokku checks:disable $TEST_APP urgentworker,worker" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_DISABLED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-disabled-list" echo "output: $output" echo "status: $status" assert_output "urgentworker,worker" - run /bin/bash -c "dokku config:get $TEST_APP DOKKU_CHECKS_SKIPPED" + run /bin/bash -c "dokku checks:report $TEST_APP --checks-skipped-list" echo "output: $output" echo "status: $status" assert_output "web,notifications" @@ -188,7 +276,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku checks:run $TEST_APP" echo "output: $output" @@ -227,7 +318,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku checks:disable $TEST_APP worker" echo "output: $output" @@ -256,9 +350,27 @@ teardown() { echo "status: $status" assert_success - run deploy_app nodejs-express dokku@dokku.me:$TEST_APP template_checks_file + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP template_checks_file echo "output: $output" echo "status: $status" - assert_output_contains "/healthcheck" 2 + assert_output_contains "/healthcheck" assert_success } + +@test "(checks) listening checks" { + if [[ "$TERM_PROGRAM" == "vscode" ]]; then + skip "environment must be running in the host namespace" + fi + + run /bin/bash -c "dokku config:set $TEST_APP ALT_PORT=5001" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Failure in name='port listening check': container listening on expected IPv4 interface with an unexpected port: expected=5000 actual=5001" + assert_output_contains "Running healthcheck name='port listening check' attempts=3 port=5000 retries=2 timeout=5 type='listening' wait=5" +} diff --git a/tests/unit/client.bats b/tests/unit/client.bats index 965cda8b99f..9d81b66a06c 100644 --- a/tests/unit/client.bats +++ b/tests/unit/client.bats @@ -4,11 +4,13 @@ load test_helper setup() { global_setup - export DOKKU_HOST=dokku.me + export "DOKKU_HOST=${DOKKU_DOMAIN}" create_app + clone_test_plugin } teardown() { + remove_test_plugin || true destroy_app unset DOKKU_HOST global_teardown @@ -22,6 +24,137 @@ teardown() { assert_exit_status 20 } +@test "(client) remote:show" { + unset DOKKU_HOST + local DOKKU_APP_PATH=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$DOKKU_APP_PATH"' INT TERM + + setup_client_repo "$DOKKU_APP_PATH" + export DOKKU_APP_PATH="$DOKKU_APP_PATH" + + # handle simple remote: dokku@example.com:app-name + run /bin/bash -c "git -C "$DOKKU_APP_PATH" remote add dokku 'dokku@example.com:$TEST_APP'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "DOKKU_APP_PATH=$DOKKU_APP_PATH ${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:show" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku-app: $TEST_APP" + assert_output_contains "dokku-git-remote: dokku" + assert_output_not_contains "dokku-host: example.com" + assert_output_not_contains "dokku-host: example.net" + assert_output_contains "dokku-host: " + assert_output_contains "dokku-port: 22" + assert_output_contains "dokku-remote-host: example.com" + assert_output_contains "dokku-ssh-user: dokku" + assert_output_contains "dokku-constructed-remote: ssh://dokku@example.com:22/$TEST_APP" + + # handle custom DOKKU_HOST (no app name detected without --app flag) + run /bin/bash -c "DOKKU_HOST="example.net" DOKKU_APP_PATH=$DOKKU_APP_PATH ${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:show" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "dokku-app: $TEST_APP" + assert_output_contains "dokku-git-remote: dokku" + assert_output_contains "dokku-host: example.net" + assert_output_contains "dokku-port: 22" + assert_output_contains "dokku-remote-host: example.net" + assert_output_contains "dokku-ssh-user: dokku" + assert_output_contains "dokku-constructed-remote: ssh://dokku@example.net:22/" + assert_output_not_contains "dokku-constructed-remote: ssh://dokku@example.net:22/$TEST_APP" + + # handle custom DOKKU_HOST (app name detected with --app flag) + run /bin/bash -c "DOKKU_HOST="example.net" DOKKU_APP_PATH=$DOKKU_APP_PATH ${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:show --app $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku-app: $TEST_APP" + assert_output_contains "dokku-git-remote: dokku" + assert_output_contains "dokku-host: example.net" + assert_output_contains "dokku-port: 22" + assert_output_contains "dokku-remote-host: example.net" + assert_output_contains "dokku-ssh-user: dokku" + assert_output_contains "dokku-constructed-remote: ssh://dokku@example.net:22/$TEST_APP" + + # handle custom DOKKU_PORT + run /bin/bash -c "DOKKU_PORT=2222 DOKKU_APP_PATH=$DOKKU_APP_PATH ${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:show" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku-app: $TEST_APP" + assert_output_contains "dokku-git-remote: dokku" + assert_output_not_contains "dokku-host: example.com" + assert_output_not_contains "dokku-host: example.net" + assert_output_contains "dokku-host: " + assert_output_contains "dokku-port: 2222" + assert_output_contains "dokku-remote-host: example.com" + assert_output_contains "dokku-ssh-user: dokku" + assert_output_contains "dokku-constructed-remote: ssh://dokku@example.com:2222/$TEST_APP" + + # handle remote with scheme (no port): ssh://dokku@example.com/app-name + run /bin/bash -c "git -C "$DOKKU_APP_PATH" remote set-url dokku 'ssh://dokku@example.com:22/$TEST_APP'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "DOKKU_APP_PATH=$DOKKU_APP_PATH ${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:show" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku-app: $TEST_APP" + assert_output_contains "dokku-git-remote: dokku" + assert_output_not_contains "dokku-host: example.com" + assert_output_not_contains "dokku-host: example.net" + assert_output_contains "dokku-host: " + assert_output_contains "dokku-port: 22" + assert_output_contains "dokku-remote-host: example.com" + assert_output_contains "dokku-ssh-user: dokku" + assert_output_contains "dokku-constructed-remote: ssh://dokku@example.com:22/$TEST_APP" + + # handle remote with scheme (with port): ssh://dokku@example.com:2222/app-name + run /bin/bash -c "git -C "$DOKKU_APP_PATH" remote set-url dokku 'ssh://dokku@example.com:2222/$TEST_APP'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "DOKKU_APP_PATH=$DOKKU_APP_PATH ${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:show" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku-app: $TEST_APP" + assert_output_contains "dokku-git-remote: dokku" + assert_output_not_contains "dokku-host: example.com" + assert_output_not_contains "dokku-host: example.net" + assert_output_contains "dokku-host: " + assert_output_contains "dokku-port: 2222" + assert_output_contains "dokku-remote-host: example.com" + assert_output_contains "dokku-ssh-user: dokku" + assert_output_contains "dokku-constructed-remote: ssh://dokku@example.com:2222/$TEST_APP" + + # #8093: ssh://dokku@pipescan.shipit.dev:2222/pipescan + run /bin/bash -c "git -C "$DOKKU_APP_PATH" remote set-url dokku 'ssh://dokku@pipescan.shipit.dev:2222/pipescan'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "DOKKU_APP_PATH=$DOKKU_APP_PATH ${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:show" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku-app: pipescan" + assert_output_contains "dokku-git-remote: dokku" + assert_output_not_contains "dokku-host: example.com" + assert_output_not_contains "dokku-host: example.net" + assert_output_contains "dokku-host: " + assert_output_contains "dokku-port: 2222" + assert_output_contains "dokku-remote-host: pipescan.shipit.dev" + assert_output_contains "dokku-ssh-user: dokku" + assert_output_contains "dokku-constructed-remote: ssh://dokku@pipescan.shipit.dev:2222/pipescan" +} + @test "(client) no args should print help" { # dokku container is not run with a TTY on GitHub Actions so we don't get normal output # https://github.com/actions/runner/issues/241 @@ -134,29 +267,29 @@ teardown() { } @test "(client) domains:add" { - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP www.test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success } @test "(client) domains:remove" { - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:remove $TEST_APP test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:remove $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" - refute_line "test.app.dokku.me" + refute_line "test.app.${DOKKU_DOMAIN}" } @test "(client) domains:clear" { - run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -171,7 +304,10 @@ teardown() { # # looks like docker exec is built to work with docker-under-libcontainer, # # but we're using docker-under-lxc. I don't have an estimated time for the fix, sorry # skip "circleci does not support docker exec at the moment." -# deploy_app +# run deploy_app +# echo "output: $output" +# echo "status: $status" +# assert_success # run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh ps $TEST_APP | grep -q 'node web.js'" # echo "output: $output" # echo "status: $status" @@ -179,7 +315,10 @@ teardown() { # } @test "(client) ps:start" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh ps:stop $TEST_APP" echo "output: $output" echo "status: $status" @@ -189,7 +328,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -197,13 +336,16 @@ teardown() { } @test "(client) ps:stop" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh ps:stop $TEST_APP" echo "output: $output" echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_failure @@ -211,15 +353,126 @@ teardown() { } @test "(client) ps:restart" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh ps:restart $TEST_APP" echo "output: $output" echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success done } + +@test "(client) remote management commands" { + setup_client_repo + local test_app_name=test-apps-create-with-name + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh apps:create $test_app_name" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku" + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:list" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku" + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:set dokku2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku2" + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:list" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku" + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:add dokku2 dokku@${DOKKU_DOMAIN}:dokku2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:list" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$(printf "dokku\ndokku2")" + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:remove dokku2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:list" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku" + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:unset" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote:unset" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh remote" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku" +} + +@test "(client) test-args" { + run /bin/bash -c "dokku plugin:install $TEST_PLUGIN_GIT_REPO --name $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku smoke-test-plugin:args bash -c 'echo Hello'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "triggered smoke-test-plugin:args with args: smoke-test-plugin:args, bash, -c, echo Hello" + + run /bin/bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh 'smoke-test-plugin:args bash -c \"echo Hello\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "triggered smoke-test-plugin:args with args: smoke-test-plugin:args, bash, -c, echo Hello" +} + +setup_client_repo() { + declare TMP="${1:-$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX")}" + rmdir "$TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/nodejs-express" "$TMP" + cd "$TMP" || exit 1 + git init + git config user.email "robot@example.com" + git config user.name "Test Robot" + + [[ -f gitignore ]] && mv gitignore .gitignore + git add . + git commit -m 'initial commit' +} diff --git a/tests/unit/config-oddities.bats b/tests/unit/config-oddities.bats index 6a06761fc9c..7ec94b7f1d1 100644 --- a/tests/unit/config-oddities.bats +++ b/tests/unit/config-oddities.bats @@ -4,16 +4,16 @@ load test_helper setup() { global_setup - [[ -f ${DOKKU_ROOT}/ENV ]] && mv -f ${DOKKU_ROOT}/ENV ${DOKKU_ROOT}/ENV.bak - sudo -H -u dokku /bin/bash -c "echo 'export global_test=true' > ${DOKKU_ROOT}/ENV" + mkdir -p "${DOKKU_LIB_ROOT}/config/--global" + [[ -f ${DOKKU_LIB_ROOT}/config/--global/ENV ]] && mv -f ${DOKKU_LIB_ROOT}/config/--global/ENV ${DOKKU_LIB_ROOT}/config/--global/ENV.bak + sudo -H -u dokku /bin/bash -c "echo 'export global_test=true' > ${DOKKU_LIB_ROOT}/config/--global/ENV" create_app } teardown() { destroy_app - ls -la ${DOKKU_ROOT} - if [[ -f ${DOKKU_ROOT}/ENV.bak ]];then - mv -f ${DOKKU_ROOT}/ENV.bak ${DOKKU_ROOT}/ENV + if [[ -f ${DOKKU_LIB_ROOT}/config/--global/ENV.bak ]]; then + mv -f ${DOKKU_LIB_ROOT}/config/--global/ENV.bak ${DOKKU_LIB_ROOT}/config/--global/ENV fi global_teardown } diff --git a/tests/unit/config.bats b/tests/unit/config.bats index 4fc522624a8..314c5a47361 100644 --- a/tests/unit/config.bats +++ b/tests/unit/config.bats @@ -4,36 +4,43 @@ load test_helper setup() { global_setup - [[ -f ${DOKKU_ROOT}/ENV ]] && mv -f ${DOKKU_ROOT}/ENV ${DOKKU_ROOT}/ENV.bak - sudo -H -u dokku /bin/bash -c "echo 'export global_test=true' > ${DOKKU_ROOT}/ENV" + mkdir -p "${DOKKU_LIB_ROOT}/config/--global" + [[ -f ${DOKKU_LIB_ROOT}/config/--global/ENV ]] && mv -f ${DOKKU_LIB_ROOT}/config/--global/ENV ${DOKKU_LIB_ROOT}/config/--global/ENV.bak + sudo -H -u dokku /bin/bash -c "echo 'export global_test=true' > ${DOKKU_LIB_ROOT}/config/--global/ENV" create_app } teardown() { destroy_app - ls -la ${DOKKU_ROOT} - if [[ -f ${DOKKU_ROOT}/ENV.bak ]];then - mv -f ${DOKKU_ROOT}/ENV.bak ${DOKKU_ROOT}/ENV + if [[ -f ${DOKKU_LIB_ROOT}/config/--global/ENV.bak ]]; then + mv -f ${DOKKU_LIB_ROOT}/config/--global/ENV.bak ${DOKKU_LIB_ROOT}/config/--global/ENV fi global_teardown } @test "(config) config:help" { + run /bin/bash -c "dokku config --help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage global and app-specific config vars" + help_output="$output" + run /bin/bash -c "dokku config:help" echo "output: $output" echo "status: $status" assert_output_contains "Manage global and app-specific config vars" + assert_output "$help_output" } @test "(config) config:set --global" { - run ssh dokku@dokku.me config:set --global test_var=true test_var2=\"hello world\" test_var3='double\"quotes' + run ssh "dokku@$DOKKU_DOMAIN" config:set --global test_var=true test_var2=\"hello world\" test_var3='double\"quotes' echo "output: $output" echo "status: $status" assert_success } @test "(config) config:get --global" { - run ssh dokku@dokku.me config:set --global test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" test_var4='double\"quotes' + run ssh "dokku@$DOKKU_DOMAIN" config:set --global test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" test_var4='double\"quotes' echo "output: $output" echo "status: $status" assert_success @@ -52,7 +59,7 @@ teardown() { } @test "(config) config:unset --global" { - run ssh dokku@dokku.me config:set --global test_var=true test_var2=\"hello world\" + run ssh "dokku@$DOKKU_DOMAIN" config:set --global test_var=true test_var2=\"hello world\" echo "output: $output" echo "status: $status" assert_success @@ -67,11 +74,286 @@ teardown() { run /bin/bash -c "dokku config:get --global test_var" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists +} + +@test "(config) config:import stdin" { + run /bin/bash -c "dokku config:set $TEST_APP first_key=first_value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "echo 'second_key=second_value' | dokku config:import $TEST_APP -" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "first_value" + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "second_value" + + run /bin/bash -c "echo 'test_var=TESTING2' | dokku config:import $TEST_APP -" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "first_value" + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "second_value" + run /bin/bash -c "dokku config:get $TEST_APP test_var" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "TESTING2" + + run /bin/bash -c "echo 'test_var=TESTING3' | dokku config:import --replace $TEST_APP -" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_failure + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_failure + run /bin/bash -c "dokku config:get $TEST_APP test_var" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "TESTING3" +} + +@test "(config) config:import file" { + local TMP_FILE=$(mktemp "/tmp/config-import.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM + + run /bin/bash -c "dokku config:set $TEST_APP first_key=first_value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "echo 'second_key=second_value' > $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:import $TEST_APP $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "chown dokku:dokku $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:import $TEST_APP $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "first_value" + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "second_value" + run /bin/bash -c "rm -f $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "echo 'test_var=TESTING2' > $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "chown dokku:dokku $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:import $TEST_APP $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "first_value" + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "second_value" + run /bin/bash -c "dokku config:get $TEST_APP test_var" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "TESTING2" + run /bin/bash -c "rm -f $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "echo 'test_var=TESTING3' > $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "chown dokku:dokku $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:import --replace $TEST_APP $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_failure + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_failure + run /bin/bash -c "dokku config:get $TEST_APP test_var" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "TESTING3" + run /bin/bash -c "rm -f $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(config) config:import file json" { + local TMP_FILE=$(mktemp "/tmp/config-import.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM + + run /bin/bash -c "dokku config:set $TEST_APP first_key=first_value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "echo '{\"second_key\": \"second_value\"}' > $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:import --format json $TEST_APP $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "chown dokku:dokku $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:import --format json $TEST_APP $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "first_value" + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "second_value" + run /bin/bash -c "rm -f $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "echo '{\"test_var\": \"TESTING2\"}' > $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "chown dokku:dokku $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:import --format json $TEST_APP $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "first_value" + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "second_value" + run /bin/bash -c "dokku config:get $TEST_APP test_var" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "TESTING2" + run /bin/bash -c "rm -f $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "echo '{\"test_var\": \"TESTING3\"}' > $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "chown dokku:dokku $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:import --replace --format json $TEST_APP $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku config:get $TEST_APP first_key" + echo "output: $output" + echo "status: $status" + assert_failure + run /bin/bash -c "dokku config:get $TEST_APP second_key" + echo "output: $output" + echo "status: $status" + assert_failure + run /bin/bash -c "dokku config:get $TEST_APP test_var" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "TESTING3" + run /bin/bash -c "rm -f $TMP_FILE" + echo "output: $output" + echo "status: $status" + assert_success } @test "(config) config:set/get" { - run ssh dokku@dokku.me config:set $TEST_APP test_var1=true test_var2=\"hello world\" test_var3='double\"quotes' + run ssh "dokku@$DOKKU_DOMAIN" config:set $TEST_APP test_var1=true test_var2=\"hello world\" test_var3='double\"quotes' echo "output: $output" echo "status: $status" assert_success @@ -117,8 +399,59 @@ teardown() { assert_output "hello world" } +@test "(config) config:set --encoded preserves decoded bytes" { + local encoded_echo encoded_printf encoded_echo_n encoded_cat tmpfile + encoded_echo="$(echo "myvalue" | base64 -w 0)" + encoded_printf="$(printf '%s' "myvalue" | base64 -w 0)" + encoded_echo_n="$(echo -n "myvalue" | base64 -w 0)" + tmpfile="$(mktemp)" + printf 'filecontent' >"$tmpfile" + encoded_cat="$(cat "$tmpfile" | base64 -w 0)" + rm -f "$tmpfile" + + run /bin/bash -c "dokku config:set --encoded --no-restart $TEST_APP test_echo=$encoded_echo" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:get $TEST_APP test_echo | wc -c" + echo "output: $output" + echo "status: $status" + assert_output "9" + + run /bin/bash -c "dokku config:set --encoded --no-restart $TEST_APP test_printf=$encoded_printf" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:get $TEST_APP test_printf | wc -c" + echo "output: $output" + echo "status: $status" + assert_output "8" + + run /bin/bash -c "dokku config:set --encoded --no-restart $TEST_APP test_echo_n=$encoded_echo_n" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:get $TEST_APP test_echo_n | wc -c" + echo "output: $output" + echo "status: $status" + assert_output "8" + + run /bin/bash -c "dokku config:set --encoded --no-restart $TEST_APP test_cat=$encoded_cat" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:get $TEST_APP test_cat | wc -c" + echo "output: $output" + echo "status: $status" + assert_output "12" +} + @test "(config) config:clear" { - run ssh dokku@dokku.me config:set $TEST_APP test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" + run ssh "dokku@$DOKKU_DOMAIN" config:set $TEST_APP test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" echo "output: $output" echo "status: $status" assert_success @@ -129,11 +462,11 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP test_var" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists } @test "(config) config:unset" { - run ssh dokku@dokku.me config:set $TEST_APP test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" + run ssh "dokku@$DOKKU_DOMAIN" config:set $TEST_APP test_var=true test_var2=\"hello world\" test_var3=\"with\\nnewline\" echo "output: $output" echo "status: $status" assert_success @@ -148,7 +481,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP test_var" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku config:get $TEST_APP test_var3" echo "output: $output" echo "status: $status" @@ -160,7 +493,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP test_var3" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku config:unset $TEST_APP" echo "output: $output" echo "status: $status" @@ -204,10 +537,15 @@ teardown() { @test "(config) global config (dockerfile)" { run deploy_app dockerfile - run /bin/bash -c "dokku run $TEST_APP env | grep -E '^global_test=true'" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "dokku run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global_test=true" } @test "(config) config:show" { diff --git a/tests/unit/core_1.bats b/tests/unit/core_1.bats index a712916b536..b28d3c51035 100644 --- a/tests/unit/core_1.bats +++ b/tests/unit/core_1.bats @@ -16,7 +16,10 @@ teardown() { } @test "(core) remove exited containers" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success # make sure we have many exited containers of the same 'type' run /bin/bash -c "for cnt in 1 2 3; do dokku run $TEST_APP echo $TEST_APP; done" @@ -34,7 +37,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - sleep 5 # wait for dokku cleanup to happen in the background + sleep 5 # wait for dokku cleanup to happen in the background run /bin/bash -c "docker inspect $RANDOM_RUN_CID" echo "output: $output" @@ -43,67 +46,77 @@ teardown() { docker rm $RANDOM_RUN_CID } -@test "(core) port exposure (dockerfile raw port)" { - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - cat< $DOCKERFILE -EXPOSE 3001/udp -EXPOSE 3003 -EXPOSE 3000/tcp -EOF - run get_dockerfile_exposed_ports $DOCKERFILE +@test "(core) image type detection (herokuish default user)" { + run deploy_app echo "output: $output" echo "status: $status" - assert_output "3001/udp 3003 3000/tcp" -} + assert_success -@test "(core) port exposure (dockerfile tcp port)" { - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - cat< $DOCKERFILE -EXPOSE 3001/udp -EXPOSE 3000/tcp -EXPOSE 3003 -EOF - run get_dockerfile_exposed_ports $DOCKERFILE + run /bin/bash -c "dokku plugin:trigger builder-image-is-herokuish $TEST_APP dokku/$TEST_APP" echo "output: $output" echo "status: $status" - assert_output "3001/udp 3000/tcp 3003" + assert_output "true" } -@test "(core) image type detection (herokuish default user)" { - export DOKKU_ROOT - deploy_app - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - source "$PLUGIN_CORE_AVAILABLE_PATH/config/functions" - - run is_image_herokuish_based "dokku/$TEST_APP" "$TEST_APP" +@test "(core) image type detection (herokuish custom user)" { + run deploy_app echo "output: $output" echo "status: $status" assert_success -} -@test "(core) image type detection (herokuish custom user)" { - export DOKKU_ROOT - deploy_app - CID=$(< "$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1") + CID=$(<"$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1") docker commit --change "ENV USER postgres" "$CID" "dokku/${TEST_APP}:latest" - dokku config:set --no-restart "$TEST_APP" DOKKU_APP_USER=postgres + run /bin/bash -c "dokku config:set --no-restart $TEST_APP DOKKU_APP_USER=postgres" + echo "output: $output" + echo "status: $status" + assert_success + source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/config/functions" - run is_image_herokuish_based "dokku/$TEST_APP" "$TEST_APP" + run /bin/bash -c "dokku plugin:trigger builder-image-is-herokuish $TEST_APP dokku/$TEST_APP" echo "output: $output" echo "status: $status" - assert_success + assert_output "true" } @test "(core) image type detection (dockerfile)" { - export DOKKU_ROOT - deploy_app dockerfile - source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - source "$PLUGIN_CORE_AVAILABLE_PATH/config/functions" + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success - run is_image_herokuish_based "dokku/$TEST_APP" "$TEST_APP" + run /bin/bash -c "dokku plugin:trigger builder-image-is-herokuish $TEST_APP dokku/$TEST_APP" echo "output: $output" echo "status: $status" - assert_failure + assert_output "false" +} + +@test "(common) fn-docker-args-split does not expand injected commands" { + export DOKKU_TEST_PAYLOAD='--label x=$(id)' + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-docker-args-split \"\$DOKKU_TEST_PAYLOAD\" | tr '\\0' '\\n'" + echo "output: $output" + echo "status: $status" + assert_success + assert_line 1 'x=$(id)' + [[ "$output" != *"uid="* ]] || flunk "id command output leaked - value was expanded" + + export DOKKU_TEST_PAYLOAD='--label x=`id`' + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-docker-args-split \"\$DOKKU_TEST_PAYLOAD\" | tr '\\0' '\\n'" + echo "output: $output" + echo "status: $status" + assert_success + assert_line 1 'x=`id`' + [[ "$output" != *"uid="* ]] || flunk "id command output leaked - value was expanded" + + unset DOKKU_TEST_PAYLOAD +} + +@test "(common) fn-docker-args-split preserves quoted tokens" { + run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-docker-args-split \"--label 'a b'\" | tr '\\0' '\\n'" + echo "output: $output" + echo "status: $status" + assert_success + assert_line 0 "--label" + assert_line 1 "a b" } diff --git a/tests/unit/core_2.bats b/tests/unit/core_2.bats index ab699655d56..a0f7a4591c0 100644 --- a/tests/unit/core_2.bats +++ b/tests/unit/core_2.bats @@ -38,38 +38,88 @@ teardown() { } @test "(core) urls (non-ssl)" { - assert_urls "http://dokku.me" - build_nginx_config - assert_urls "http://${TEST_APP}.dokku.me" - add_domain "test.dokku.me" - assert_urls "http://${TEST_APP}.dokku.me" "http://test.dokku.me" + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" "http://test.${DOKKU_DOMAIN}" } @test "(core) urls (app ssl)" { - setup_test_tls - assert_urls "https://dokku.me" - build_nginx_config - assert_urls "http://${TEST_APP}.dokku.me" "https://${TEST_APP}.dokku.me" - add_domain "test.dokku.me" - assert_urls "http://${TEST_APP}.dokku.me" "http://test.dokku.me" "https://${TEST_APP}.dokku.me" "https://test.dokku.me" + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" + + run setup_test_tls + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_urls "https://${TEST_APP}.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" "https://${TEST_APP}.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}" "http://test.${DOKKU_DOMAIN}" } @test "(core) url (app ssl)" { - setup_test_tls - assert_url "https://dokku.me" - build_nginx_config - assert_url "https://${TEST_APP}.dokku.me" + run setup_test_tls + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_url "https://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(core) urls (wildcard ssl)" { - setup_test_tls wildcard - assert_urls "https://dokku.me" - build_nginx_config - assert_urls "http://${TEST_APP}.dokku.me" "https://${TEST_APP}.dokku.me" - add_domain "test.dokku.me" - assert_urls "http://${TEST_APP}.dokku.me" "http://test.dokku.me" "https://${TEST_APP}.dokku.me" "https://test.dokku.me" - add_domain "dokku.example.com" - assert_urls "http://dokku.example.com" "http://${TEST_APP}.dokku.me" "http://test.dokku.me" "https://dokku.example.com" "https://${TEST_APP}.dokku.me" "https://test.dokku.me" + run setup_test_tls wildcard + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_urls "https://${TEST_APP}.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + assert_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" "https://${TEST_APP}.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}" "http://test.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku domains:add $TEST_APP dokku.example.com" + echo "output: $output" + echo "status: $status" + assert_success + assert_urls "http://dokku.example.com" "http://${TEST_APP}.${DOKKU_DOMAIN}" "https://dokku.example.com" "https://${TEST_APP}.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}" "http://test.${DOKKU_DOMAIN}" +} + +@test "(core) release_and_deploy does not emit command not found errors" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "fn-plugin-property-get-default: command not found" + assert_output_not_contains "fn-plugin-property-get: command not found" + assert_output_not_contains "/common/property-functions: No such file or directory" } @test "(core) git-remote (off-port)" { diff --git a/tests/unit/cron.bats b/tests/unit/cron.bats index 4595cf3b205..b52a16e7154 100644 --- a/tests/unit/cron.bats +++ b/tests/unit/cron.bats @@ -14,7 +14,6 @@ version = "0.0.1" [plugin.config] EOF cp /var/lib/dokku/plugins/available/cron-entries/plugin.toml /var/lib/dokku/plugins/enabled/cron-entries/plugin.toml - } teardown() { @@ -37,29 +36,36 @@ teardown() { assert_output "$help_output" } +@test "(cron) installed" { + run /bin/bash -c "test -f /etc/sudoers.d/dokku-cron" + echo "output: $output" + echo "status: $status" + assert_failure +} + @test "(cron) invalid [missing-keys]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_invalid + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid echo "output: $output" echo "status: $status" assert_failure } @test "(cron) invalid [schedule]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_invalid_schedule + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid_schedule echo "output: $output" echo "status: $status" assert_failure } @test "(cron) invalid [seconds]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_invalid_schedule_seconds + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_invalid_schedule_seconds echo "output: $output" echo "status: $status" assert_failure } @test "(cron) create [empty]" { - run deploy_app python dokku@dokku.me:$TEST_APP + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP echo "output: $output" echo "status: $status" assert_success @@ -71,54 +77,284 @@ teardown() { } @test "(cron) create [single-verbose]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_valid + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single echo "output: $output" echo "status: $status" assert_success + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" echo "output: $output" echo "status: $status" assert_success - assert_output_contains "python task.py schedule" + assert_output_contains "dokku cron:run $TEST_APP $cron_id" + assert_output_contains "python3 task.py schedule" 0 } @test "(cron) create [single-short]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_valid_short + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_short echo "output: $output" echo "status: $status" assert_success + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" echo "output: $output" echo "status: $status" assert_success - assert_output_contains "python task.py daily" + assert_output_contains "dokku cron:run $TEST_APP $cron_id" + assert_output_contains "python3 task.py daily" 0 +} + +@test "(cron:report) --global --format json" { + run /bin/bash -c "dokku cron:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-maintenance\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-maintenance\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-maintenance\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-maintenance\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-maintenance\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-mailfrom\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-mailfrom\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-mailfrom\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-mailfrom\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-global-mailto\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"global-mailto\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"cron-computed-mailto\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r 'has(\"computed-mailto\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report --global" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(cron:set) --global mailto" { + run /bin/bash -c "dokku cron:set --global mailto admin@example.com" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "unknown flag" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailto\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "admin@example.com" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailto\"'" + assert_success + assert_output "admin@example.com" + + run /bin/bash -c "dokku cron:set --global mailto" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "unknown flag" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailto\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailto\"'" + assert_success + assert_output "" +} + +@test "(cron:set) --global mailfrom" { + run /bin/bash -c "dokku cron:set --global mailfrom dokku@example.com" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "unknown flag" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailfrom\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku@example.com" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailfrom\"'" + assert_success + assert_output "dokku@example.com" + + run /bin/bash -c "dokku cron:set --global mailfrom" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "unknown flag" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-mailfrom\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-computed-mailfrom\"'" + assert_success + assert_output "" +} + +@test "(cron:set) --global maintenance" { + run /bin/bash -c "dokku cron:set --global maintenance true" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "unknown flag" + + run /bin/bash -c "dokku cron:report --global --format json | jq -r '.\"cron-global-maintenance\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:set --global maintenance" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "unknown flag" +} + +@test "(cron:report) maintenance raw vs computed vs global" { + run /bin/bash -c "dokku cron:set --global maintenance" + assert_success + + run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-maintenance\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku cron:set --global maintenance true" + assert_success + + run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:set $TEST_APP maintenance true" + assert_success + + run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-maintenance\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-global-maintenance\"'" + assert_success + assert_output "true" + + # cron computed-maintenance returns "true" if either per-app or global is + # "true" (cron's logic at plugins/cron/report.go::reportComputedMaintenance + # is OR-not-override: any "true" wins). This is intentional for maintenance + # mode - a per-app "false" does NOT override a global "true". + run /bin/bash -c "dokku cron:report $TEST_APP --format json | jq -r '.\"cron-computed-maintenance\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku cron:set $TEST_APP maintenance" + assert_success + + run /bin/bash -c "dokku cron:set --global maintenance" + assert_success +} + +@test "(cron:set) --global rejects task maintenance properties" { + run /bin/bash -c "dokku cron:set --global maintenance.fakeid true" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Task maintenance properties cannot be set globally" } @test "(cron) create [multiple]" { - run deploy_app python dokku@dokku.me:$TEST_APP template_cron_file_valid_multiple + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_multiple echo "output: $output" echo "status: $status" assert_success + first_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + second_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')" + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" echo "output: $output" echo "status: $status" assert_success - assert_output_contains "python task.py first" - assert_output_contains "python task.py second" + assert_output_contains "dokku cron:run $TEST_APP $first_id" + assert_output_contains "dokku cron:run $TEST_APP $second_id" + assert_output_contains "python3 task.py first" 0 + assert_output_contains "python3 task.py second" 0 } @test "(cron) injected entries" { - echo "echo '@daily;/bin/true'" > /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + echo "echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries - run /bin/bash -c "dokku plugin:trigger cron-write" + run /bin/bash -c "dokku plugin:trigger scheduler-cron-write" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku cron:list --global --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output '[{"id":"5cruaotm4yzzpnjlsdunblj8qyjp","command":"/bin/true","global":true,"schedule":"@daily","concurrency_policy":"","app-in-maintenance":false,"task-in-maintenance":false,"maintenance":false}]' + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" echo "output: $output" echo "status: $status" @@ -126,15 +362,14 @@ teardown() { assert_output_contains "@daily /bin/true" # log file - echo "echo '@daily;/bin/true;/var/log/dokku/log.log'" > /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + echo "echo '@daily;/bin/true;/var/log/dokku/log.log'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries - run /bin/bash -c "dokku plugin:trigger cron-write" + run /bin/bash -c "dokku plugin:trigger scheduler-cron-write" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" echo "output: $output" echo "status: $status" @@ -142,15 +377,14 @@ teardown() { assert_output_contains "@daily /bin/true &>> /var/log/dokku/log.log" # specify matching scheduler - echo "[[ \$1 == 'docker-local' ]] && echo '@daily;/bin/true'" > /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + echo "[[ \$1 == 'docker-local' ]] && echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries - run /bin/bash -c "dokku plugin:trigger cron-write" + run /bin/bash -c "dokku plugin:trigger scheduler-cron-write" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" echo "output: $output" echo "status: $status" @@ -158,21 +392,265 @@ teardown() { assert_output_contains "@daily /bin/true" # specify non-matching scheduler - echo "[[ \$1 == 'kubernetes' ]] && echo '@daily;/bin/true'" > /var/lib/dokku/plugins/enabled/cron-entries/cron-entries + echo "[[ \$1 == 'kubernetes' ]] && echo '@daily;/bin/true'" >/var/lib/dokku/plugins/enabled/cron-entries/cron-entries chmod +x /var/lib/dokku/plugins/enabled/cron-entries/cron-entries - run /bin/bash -c "dokku plugin:trigger cron-write" + run /bin/bash -c "dokku plugin:trigger scheduler-cron-write" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" + echo "output: $output" + echo "status: $status" + assert_failure +} + +@test "(cron) cron:list --format json" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku cron:list $TEST_APP --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists +} + +@test "(cron) cron:run" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid + echo "output: $output" + echo "status: $status" + assert_success + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'schedule']" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "['task.py', 'schedule', 'now']" +} + +@test "(cron) cron:run concurrency_policy forbid" { + run deploy_app dockerfile dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_concurrency_forbid + echo "output: $output" + echo "status: $status" + assert_success + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo cron $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker ps --filter "label=com.dokku.cron-id=$cron_id" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.concurrency-policy\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "forbid" + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_output_contains "currently has a cron lock in place for $cron_id" + assert_failure +} + +@test "(cron) cron:suspend cron:resume" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_multiple + echo "output: $output" + echo "status: $status" + assert_success + + first_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + second_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[1].id')" + + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku cron:run $TEST_APP $first_id" + assert_output_contains "dokku cron:run $TEST_APP $second_id" + + run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku cron:suspend $TEST_APP $first_id" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id" echo "output: $output" echo "status: $status" assert_success + assert_output "true" + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku cron:run $TEST_APP $first_id" 0 + assert_output_contains "dokku cron:run $TEST_APP $second_id" + + run /bin/bash -c "dokku cron:resume $TEST_APP $first_id" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku cron:report $TEST_APP --cron-maintenance-$first_id" + echo "output: $output" + echo "status: $status" + assert_failure run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" echo "output: $output" echo "status: $status" + assert_success + assert_output_contains "dokku cron:run $TEST_APP $first_id" + assert_output_contains "dokku cron:run $TEST_APP $second_id" +} + +@test "(cron) invalid [command]" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_injection + echo "output: $output" + echo "status: $status" assert_failure } +@test "(cron) crontab format [no raw command leakage]" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_valid_single + echo "output: $output" + echo "status: $status" + assert_success + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + + run /bin/bash -c "cat /var/spool/cron/crontabs/dokku" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku cron:run $TEST_APP $cron_id" + # User command must never appear verbatim in the crontab - the crontab + # only references the cron ID, and cron:run resolves the command at run + # time and exec's it inside the container. + assert_output_contains "python3 task.py schedule" 0 +} + +@test "(cron) container labels regression" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP template_cron_file_long_running + echo "output: $output" + echo "status: $status" + assert_success + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + + run /bin/bash -c "dokku cron:run $TEST_APP $cron_id --detach" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.cron-id\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_id" + + run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.concurrency-policy\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "allow" + + run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.container-type\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "cron" + + run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.app-name\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP" + + run /bin/bash -c "docker ps --filter \"label=com.dokku.cron-id=$cron_id\" -q | xargs docker inspect -f '{{ index .Config.Labels \"com.dokku.active-deadline-seconds\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "86400" +} + +template_cron_file_long_running() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "injecting long-running cron app.json -> $APP_REPO_DIR/app.json" + cat <"$APP_REPO_DIR/app.json" +{ + "cron": [ + { + "command": "sleep 30", + "schedule": "0 0 * * *" + } + ] +} +EOF +} + +template_cron_file_injection() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "injecting injection-attempt cron app.json -> $APP_REPO_DIR/app.json" + cat <"$APP_REPO_DIR/app.json" +{ + "cron": [ + { + "command": "echo CRON_OK; echo hi > /tmp/appjson-injection-test.txt", + "schedule": "* * * * *" + } + ] +} +EOF +} + template_cron_file_invalid() { local APP="$1" local APP_REPO_DIR="$2" @@ -198,7 +676,7 @@ template_cron_file_invalid_schedule() { { "cron": [ { - "command": "python task.py", + "command": "python3 task.py", "schedule": "@nonstandard" } ] @@ -215,7 +693,7 @@ template_cron_file_invalid_schedule_seconds() { { "cron": [ { - "command": "python task.py", + "command": "python3 task.py", "schedule": "0 5 * * * *" } ] @@ -232,14 +710,34 @@ template_cron_file_valid() { { "cron": [ { - "command": "python task.py schedule", + "command": "python3 task.py schedule", "schedule": "5 5 5 5 5" + }, + { + "command": "python3 task.py schedule now", + "schedule": "6 5 5 5 5" } ] } EOF } +template_cron_file_valid_single() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json" + cat <"$APP_REPO_DIR/app.json" +{ + "cron": [ + { + "command": "python3 task.py schedule", + "schedule": "5 5 5 5 5" + } + ] +} +EOF +} template_cron_file_valid_short() { local APP="$1" local APP_REPO_DIR="$2" @@ -249,7 +747,7 @@ template_cron_file_valid_short() { { "cron": [ { - "command": "python task.py daily", + "command": "python3 task.py daily", "schedule": "@daily" } ] @@ -266,14 +764,32 @@ template_cron_file_valid_multiple() { { "cron": [ { - "command": "python task.py first", + "command": "python3 task.py first", "schedule": "5 5 5 5 5" }, { - "command": "python task.py second", + "command": "python3 task.py second", "schedule": "@daily" } ] } EOF } + +template_cron_file_concurrency_forbid() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "injecting valid cron app.json -> $APP_REPO_DIR/app.json" + cat <"$APP_REPO_DIR/app.json" +{ + "cron": [ + { + "command": "sleep 30", + "schedule": "0 0 * * *", + "concurrency_policy": "forbid" + } + ] +} +EOF +} diff --git a/tests/unit/docker-image-healthcheck.bats b/tests/unit/docker-image-healthcheck.bats new file mode 100644 index 00000000000..27d75caea57 --- /dev/null +++ b/tests/unit/docker-image-healthcheck.bats @@ -0,0 +1,57 @@ +#!/usr/bin/env bats + +REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)" + +NGINX_CONF="${REPO_ROOT}/docker/etc/nginx/conf.d/zz-dokku-health.conf" +FINISH_SCRIPT="${REPO_ROOT}/docker/etc/runit/runsvdir/default/dokku-restore/finish" +MY_INIT_SCRIPT="${REPO_ROOT}/docker/etc/my_init.d/10_dokku_init" +DOCKERFILE="${REPO_ROOT}/Dockerfile" + +@test "(docker-image) [healthcheck] nginx health conf is shipped via the docker overlay" { + [[ -f "$NGINX_CONF" ]] + grep -qE "listen[[:space:]]+127\.0\.0\.1:18080" "$NGINX_CONF" + grep -qE "location = /_dokku/health" "$NGINX_CONF" + grep -qE "/var/run/dokku/ready" "$NGINX_CONF" + grep -qE "default_type[[:space:]]+text/plain" "$NGINX_CONF" +} + +@test "(docker-image) [healthcheck] finish script touches sentinel after waiting for sshd and nginx" { + [[ -f "$FINISH_SCRIPT" ]] + + local sshd_line nginx_line touch_line + sshd_line=$(grep -nE "/dev/tcp/127\.0\.0\.1/22" "$FINISH_SCRIPT" | head -n1 | cut -d: -f1) + nginx_line=$(grep -nE "/dev/tcp/127\.0\.0\.1/80" "$FINISH_SCRIPT" | head -n1 | cut -d: -f1) + touch_line=$(grep -nE "touch /var/run/dokku/ready" "$FINISH_SCRIPT" | head -n1 | cut -d: -f1) + + [[ -n "$sshd_line" ]] + [[ -n "$nginx_line" ]] + [[ -n "$touch_line" ]] + ((touch_line > sshd_line)) + ((touch_line > nginx_line)) +} + +@test "(docker-image) [healthcheck] my_init clears stale sentinel on container boot" { + [[ -f "$MY_INIT_SCRIPT" ]] + + local rm_line mkdir_line plugin_line hostname_line + rm_line=$(grep -nE "rm -f /var/run/dokku/ready" "$MY_INIT_SCRIPT" | head -n1 | cut -d: -f1) + mkdir_line=$(grep -nE "mkdir -p /var/run/dokku( |$)" "$MY_INIT_SCRIPT" | head -n1 | cut -d: -f1) + plugin_line=$(grep -nE "plugin:install" "$MY_INIT_SCRIPT" | head -n1 | cut -d: -f1) + hostname_line=$(grep -nE "domains:set-global" "$MY_INIT_SCRIPT" | head -n1 | cut -d: -f1) + + [[ -n "$rm_line" ]] + [[ -n "$mkdir_line" ]] + [[ -n "$plugin_line" ]] + [[ -n "$hostname_line" ]] + ((rm_line < plugin_line)) + ((rm_line < hostname_line)) + ((mkdir_line < plugin_line)) + ((mkdir_line < hostname_line)) +} + +@test "(docker-image) [healthcheck] Dockerfile declares HEALTHCHECK with the expected endpoint" { + [[ -f "$DOCKERFILE" ]] + grep -qE "^HEALTHCHECK" "$DOCKERFILE" + grep -qE "http://127\.0\.0\.1:18080/_dokku/health" "$DOCKERFILE" + grep -qE "\-\-start-period" "$DOCKERFILE" +} diff --git a/tests/unit/docker-options-2.bats b/tests/unit/docker-options-2.bats new file mode 100644 index 00000000000..620a8a10575 --- /dev/null +++ b/tests/unit/docker-options-2.bats @@ -0,0 +1,326 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + destroy_app + global_teardown +} + +@test "(docker-options:add) --process scopes option to one process type" { + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:list $TEST_APP --process web --phase deploy" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "-p 8080:5000" + + run /bin/bash -c "dokku docker-options:list $TEST_APP --phase deploy" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "-p 8080:5000" 0 +} + +@test "(docker-options:add) multiple --process flags add to each process" { + run /bin/bash -c "dokku docker-options:add --process web --process worker $TEST_APP deploy '-v /shared:/shared'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:list $TEST_APP --process web --phase deploy" + echo "output: $output" + assert_output "-v /shared:/shared" + + run /bin/bash -c "dokku docker-options:list $TEST_APP --process worker --phase deploy" + echo "output: $output" + assert_output "-v /shared:/shared" +} + +@test "(docker-options:add) without --process keeps default-scope behaviour" { + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '-v /tmp/shared:/shared'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:list $TEST_APP --phase deploy" + echo "output: $output" + assert_output_contains "-v /tmp/shared:/shared" + + run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-deploy" + echo "output: $output" + assert_output_contains "-v /tmp/shared:/shared" +} + +@test "(docker-options:add) rejects --process for non-deploy phases" { + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP build '--shm-size 256m'" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "deploy phase" + + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP run '--shm-size 256m'" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP build,deploy '--shm-size 256m'" + echo "output: $output" + echo "status: $status" + assert_failure +} + +@test "(docker-options:add) rejects reserved _default_ process name" { + run /bin/bash -c "dokku docker-options:add --process _default_ $TEST_APP deploy '-v /tmp:/tmp'" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "_default_" +} + +@test "(docker-options:add) warns on process types missing from Procfile" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add --process nonexistent $TEST_APP deploy '-v /tmp:/tmp'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "nonexistent" +} + +@test "(docker-options:remove) --process removes from a single process" { + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:add --process worker $TEST_APP deploy '-p 9000:9000'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:remove --process web $TEST_APP deploy '-p 8080:5000'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:list $TEST_APP --process web --phase deploy" + echo "output: $output" + assert_output "" + + run /bin/bash -c "dokku docker-options:list $TEST_APP --process worker --phase deploy" + echo "output: $output" + assert_output "-p 9000:9000" +} + +@test "(docker-options:clear) --process clears only that process" { + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:add --process worker $TEST_APP deploy '-p 9000:9000'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:clear --process web $TEST_APP deploy" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:list $TEST_APP --process web --phase deploy" + echo "output: $output" + assert_output "" + + run /bin/bash -c "dokku docker-options:list $TEST_APP --process worker --phase deploy" + echo "output: $output" + assert_output "-p 9000:9000" +} + +@test "(docker-options:list) --phase is required" { + run /bin/bash -c "dokku docker-options:list $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "phase" +} + +@test "(docker-options:report) exposes dynamic per-process keys" { + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-deploy.web" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "-p 8080:5000" + + run /bin/bash -c "dokku docker-options:report $TEST_APP" + echo "output: $output" + assert_success + assert_output_contains "Docker options deploy web" + assert_output_contains "-p 8080:5000" +} + +@test "(docker-options:report) --format json carries process-scoped options" { + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '-v /logs:/logs'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:report $TEST_APP --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains '"docker-options-deploy.web"' + assert_output_contains '"deploy.web"' + assert_output_contains '"deploy.web-list"' + assert_output_contains "-p 8080:5000" 3 + assert_output_contains '"docker-options-deploy"' + assert_output_contains '"deploy"' + assert_output_contains '"deploy-list"' + assert_output_contains "-v /logs:/logs" 3 +} + +@test "(docker-options:report) --format json exposes structured -list keys" { + run /bin/bash -c "dokku docker-options:add $TEST_APP build --build-arg X=Y --link foo --link bar" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '-v /logs:/logs' '--memory=512m'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy --label 'com.example.description=my app'" + echo "output: $output" + assert_success + + run /bin/bash -c "dokku docker-options:report $TEST_APP --format json | jq -e '.\"build-list\" | length == 3'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:report $TEST_APP --format json | jq -e '.\"deploy-list\" | length == 3'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:report $TEST_APP --format json | jq -e '.\"deploy-list\" | index(\"--memory=512m\") != null'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:report $TEST_APP --format json | jq -e '.\"deploy-list\"[] | select(test(\"my app\"))'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:report $TEST_APP --format json | jq -e '.\"deploy.web-list\"[]'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "-p 8080:5000" + + run /bin/bash -c "dokku docker-options:report $TEST_APP --format json | jq -e 'has(\"docker-options-deploy-list\") | not'" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(docker-options:report) --format json emits empty -list arrays for unset phases" { + run /bin/bash -c "dokku docker-options:report $TEST_APP --format json | jq -e '.\"build-list\" == [] and .\"deploy-list\" == [] and .\"run-list\" == []'" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(docker-options) clone copies default and per-process options" { + local CLONE_APP="${TEST_APP}-clone" + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + assert_success + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '-v /logs:/logs'" + assert_success + + run /bin/bash -c "dokku apps:clone --skip-deploy $TEST_APP $CLONE_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:list $CLONE_APP --process web --phase deploy" + echo "output: $output" + assert_output "-p 8080:5000" + + run /bin/bash -c "dokku docker-options:list $CLONE_APP --phase deploy" + echo "output: $output" + assert_output_contains "-v /logs:/logs" + + dokku --force apps:destroy "$CLONE_APP" || true +} + +@test "(docker-options) rename moves default and per-process options" { + local RENAMED_APP="${TEST_APP}-renamed" + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + assert_success + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '-v /logs:/logs'" + assert_success + + run /bin/bash -c "dokku apps:rename $TEST_APP $RENAMED_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:list $RENAMED_APP --process web --phase deploy" + assert_output "-p 8080:5000" + + run /bin/bash -c "dokku docker-options:list $RENAMED_APP --phase deploy" + assert_output_contains "-v /logs:/logs" + + [[ ! -d "/var/lib/dokku/config/docker-options/$TEST_APP" ]] + + TEST_APP="$RENAMED_APP" +} + +@test "(docker-options) deploy with web-only port mapping does not break worker" { + run /bin/bash -c "dokku docker-options:add --process web $TEST_APP deploy '-p 8080:5000'" + echo "output: $output" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP web_worker_callback + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "App container failed to start" 0 + assert_output_contains "all ports are allocated" 0 + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-status-web-1" + echo "output: $output" + assert_output_contains "running" + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-status-worker-1" + echo "output: $output" + assert_output_contains "running" +} + +web_worker_callback() { + local APP="$1" + local APP_REPO_DIR="$2" + cat >"$APP_REPO_DIR/Procfile" <> $DOKKU_ROOT/$TEST_APP/DOCKER_OPTIONS_DEPLOY" - echo "output: $output" - echo "status: $status" - assert_success - run /bin/bash -c "echo '# comment' >> $DOKKU_ROOT/$TEST_APP/DOCKER_OPTIONS_DEPLOY" + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy \"-v /tmp\"" echo "output: $output" echo "status: $status" assert_success @@ -283,23 +278,38 @@ teardown() { echo "status: $status" assert_success - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect -f '{{ .Config.Volumes }}' $CID | sed -e 's:map::g' | tr -d '[]' | tr ' ' $'\n' | sort | xargs" echo "output: $output" echo "status: $status" assert_output "/tmp:{} /var/tmp:{}" } -@test "(docker-options) deploy with options [dockerfile]" { - run /bin/bash -c "dokku docker-options:add $TEST_APP deploy \"-v /var/tmp\"" +@test "(docker-options) deploy does not expand command substitution in option values [buildpacks]" { + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '--label=com.dokku.test=\$(id)'" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "echo '-v /tmp' >> $DOKKU_ROOT/$TEST_APP/DOCKER_OPTIONS_DEPLOY" + + run deploy_app echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "echo '# comment' >> $DOKKU_ROOT/$TEST_APP/DOCKER_OPTIONS_DEPLOY" + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect $CID --format '{{ index .Config.Labels \"com.dokku.test\" }}'" + echo "output: $output" + echo "status: $status" + assert_output '$(id)' + [[ "$output" != *"uid="* ]] || flunk "id command output leaked - option value was expanded" +} + +@test "(docker-options) deploy with options [dockerfile]" { + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy \"-v /var/tmp\"" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy \"-v /tmp\"" echo "output: $output" echo "status: $status" assert_success @@ -323,9 +333,9 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "One or more build-args \[PAYPAL_CLIENT_ID PAYPAL_CLIENT_MODE\] were not consumed" + # assert_output_contains "One or more build-args \[PAYPAL_CLIENT_ID PAYPAL_CLIENT_MODE\] were not consumed" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect -f '{{ .Config.Volumes }}' $CID | sed -e 's:map::g' | tr -d '[]' | tr ' ' $'\n' | sort | xargs" echo "output: $output" echo "status: $status" @@ -333,7 +343,7 @@ teardown() { } @test "(docker-options) docker-options:add (all phases over SSH)" { - run ssh dokku@dokku.me docker-options:add $TEST_APP build,deploy,run "-v /tmp" + run ssh "dokku@$DOKKU_DOMAIN" docker-options:add $TEST_APP build,deploy,run "-v /tmp" echo "output: $output" echo "status: $status" assert_success @@ -360,3 +370,85 @@ teardown() { echo "status: $status" assert_success } + +@test "(docker-options) build arguments" { + run /bin/bash -c "dokku docker-options:add $TEST_APP build '--build-arg GITHUB_TOKEN=\"hello\"'" + + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "TOKEN is: hello" 2 +} + +@test "(docker-options:add) splits multi-flag input into separate options" { + run /bin/bash -c "dokku docker-options:add $TEST_APP build --build-arg X=Y --link foo --link bar" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:list $TEST_APP --phase build" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "--build-arg X=Y" + assert_output_contains "--link foo" + assert_output_contains "--link bar" + + run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-build" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "--build-arg X=Y" + assert_output_contains "--link foo" + assert_output_contains "--link bar" +} + +@test "(docker-options:remove) symmetrically removes multi-flag input" { + run /bin/bash -c "dokku docker-options:add $TEST_APP build --build-arg X=Y --link foo --link bar" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:remove $TEST_APP build --build-arg X=Y --link foo --link bar" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:list $TEST_APP --phase build" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" +} + +@test "(docker-options:add) lifts misplaced --process out of option content" { + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy --link foo --process web" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:list $TEST_APP --process web --phase deploy" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--link foo" + + run /bin/bash -c "dokku docker-options:list $TEST_APP --phase deploy" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "--link foo" 0 +} + +@test "(docker-options) dockerfile build skips unsupported flags from multi-flag input" { + run /bin/bash -c "dokku docker-options:add $TEST_APP build --build-arg PAYPAL_CLIENT_ID=abc --link postgres" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/tests/unit/domains.bats b/tests/unit/domains.bats index 96743cfd73d..00597304454 100644 --- a/tests/unit/domains.bats +++ b/tests/unit/domains.bats @@ -28,21 +28,51 @@ teardown() { assert_output "$help_output" } +@test "(domains:report) --global --format json" { + run /bin/bash -c "dokku domains:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:report --global --format json | jq -r 'keys[]' | grep -v 'global-' | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "0" + + run /bin/bash -c "dokku domains:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global domains information" + + run /bin/bash -c "dokku domains:report --global --domains-global-enabled" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku domains:report --global --domains-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + @test "(domains) domains" { - dokku domains:setup $TEST_APP - run /bin/bash -c "dokku domains:report $TEST_APP 2>/dev/null | grep ${TEST_APP}.dokku.me" + run /bin/bash -c "dokku domains:report $TEST_APP 2>/dev/null | grep ${TEST_APP}.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" - assert_output_contains "${TEST_APP}.dokku.me" + assert_output_contains "${TEST_APP}.${DOKKU_DOMAIN}" } @test "(domains) domains:add" { - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP 2.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP 2.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -52,7 +82,12 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP .dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP .${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP _" echo "output: $output" echo "status: $status" assert_success @@ -61,13 +96,14 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains www.test.app.dokku.me - assert_output_contains 2.app.dokku.me + assert_output_contains "www.test.app.${DOKKU_DOMAIN}" + assert_output_contains "2.app.${DOKKU_DOMAIN}" assert_output_contains a--domain.with--hyphens + assert_output_contains _ } @test "(domains) domains:add (multiple)" { - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me 2.app.dokku.me a--domain.with--hyphens" + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN} 2.app.${DOKKU_DOMAIN} a--domain.with--hyphens" echo "output: $output" echo "status: $status" assert_success @@ -76,37 +112,37 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains www.test.app.dokku.me - assert_output_contains 2.app.dokku.me + assert_output_contains "www.test.app.${DOKKU_DOMAIN}" + assert_output_contains "2.app.${DOKKU_DOMAIN}" assert_output_contains a--domain.with--hyphens } @test "(domains) domains:add (duplicate)" { - run /bin/bash -c "dokku domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success } @test "(domains) domains:add (invalid)" { - run /bin/bash -c "dokku domains:add $TEST_APP http://test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP http://test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_failure } @test "(domains) domains:remove" { - run /bin/bash -c "dokku domains:add $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:remove $TEST_APP test.app.dokku.me" + run /bin/bash -c "dokku domains:remove $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -115,16 +151,16 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains test.app.dokku.me 0 + assert_output_contains "test.app.${DOKKU_DOMAIN}" 0 } @test "(domains) domains:remove (multiple)" { - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me test.app.dokku.me 2.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN} test.app.${DOKKU_DOMAIN} 2.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:remove $TEST_APP www.test.app.dokku.me test.app.dokku.me" + run /bin/bash -c "dokku domains:remove $TEST_APP www.test.app.${DOKKU_DOMAIN} test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -133,36 +169,42 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains www.test.app.dokku.me 0 - assert_output_contains test.app.dokku.me 0 - assert_output_contains 2.app.dokku.me + assert_output_contains "www.test.app.${DOKKU_DOMAIN}" 0 + assert_output_contains "test.app.${DOKKU_DOMAIN}" 0 + assert_output_contains "2.app.${DOKKU_DOMAIN}" } @test "(domains) domains:remove (wildcard domain)" { - run /bin/bash -c "dokku domains:add $TEST_APP *.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP '*.${DOKKU_DOMAIN}'" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:remove $TEST_APP *.dokku.me" + run /bin/bash -c "dokku domains:report $TEST_APP --domains-app-vhosts" echo "output: $output" echo "status: $status" assert_success + assert_output "$TEST_APP.${DOKKU_DOMAIN} *.${DOKKU_DOMAIN}" - run /bin/bash -c "dokku domains:report $TEST_APP 2>/dev/null" + run /bin/bash -c "dokku domains:remove $TEST_APP '*.${DOKKU_DOMAIN}'" echo "output: $output" echo "status: $status" assert_success - assert_output_contains *.dokku.me 0 + + run /bin/bash -c "dokku domains:report $TEST_APP --domains-app-vhosts" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP.${DOKKU_DOMAIN}" } @test "(domains) domains:set" { - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN} test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:set $TEST_APP 2.app.dokku.me a--domain.with--hyphens" + run /bin/bash -c "dokku domains:set $TEST_APP 2.app.${DOKKU_DOMAIN} a--domain.with--hyphens" echo "output: $output" echo "status: $status" assert_success @@ -171,14 +213,14 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains www.test.app.dokku.me 0 - assert_output_contains test.app.dokku.me 0 - assert_output_contains 2.app.dokku.me + assert_output_contains "www.test.app.${DOKKU_DOMAIN}" 0 + assert_output_contains "test.app.${DOKKU_DOMAIN}" 0 + assert_output_contains "2.app.${DOKKU_DOMAIN}" assert_output_contains a--domain.with--hyphens } -@test "(domains) domains:clear" { - run /bin/bash -c "dokku domains:add $TEST_APP test.app.dokku.me" +@test "(domains) domains:reset" { + run /bin/bash -c "dokku domains:add $TEST_APP test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -188,7 +230,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:clear $TEST_APP" + run /bin/bash -c "dokku domains:reset $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -197,11 +239,11 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains test.app.dokku.me 0 + assert_output_contains "test.app.${DOKKU_DOMAIN}" 0 } @test "(domains) domains:add-global" { - run /bin/bash -c "dokku domains:add-global global.dokku.me" + run /bin/bash -c "dokku domains:add-global global.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -211,29 +253,29 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:report 2>/dev/null | grep -qw 'global.dokku.me'" + run /bin/bash -c "dokku domains:report 2>/dev/null | grep -qw 'global.${DOKKU_DOMAIN}'" echo "output: $output" echo "status: $status" assert_success } @test "(domains) domains:add-global (multiple)" { - run /bin/bash -c "dokku domains:add-global global1.dokku.me global2.dokku.me global3.dokku.me" + run /bin/bash -c "dokku domains:add-global global1.${DOKKU_DOMAIN} global2.${DOKKU_DOMAIN} global3.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global1.dokku.me" + run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global1.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global2.dokku.me" + run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global3.dokku.me" + run /bin/bash -c "dokku domains:report 2>/dev/null | grep -q global3.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -245,7 +287,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku domains:add-global global.dokku.me" + run /bin/bash -c "dokku domains:add-global global.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success @@ -255,58 +297,74 @@ teardown() { echo "status: $status" assert_success refute_line "global.dokku.invalid" - refute_line "global.dokku.me" + refute_line "global.${DOKKU_DOMAIN}" } @test "(domains) domains:remove-global" { - run /bin/bash -c "dokku domains:add-global global.dokku.me" + run /bin/bash -c "dokku domains:add-global global.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:remove-global global.dokku.me" + run /bin/bash -c "dokku domains:remove-global global.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" - refute_line "global.dokku.me" + refute_line "global.${DOKKU_DOMAIN}" } @test "(domains) domains (multiple global domains)" { - run /bin/bash -c "dokku domains:add-global global1.dokku.me global2.dokku.me" + run /bin/bash -c "dokku domains:add-global global1.${DOKKU_DOMAIN} global2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - dokku domains:setup $TEST_APP + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success + + run create_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku domains:report $TEST_APP 2>/dev/null" echo "output: $output" echo "status: $status" assert_success - assert_output_contains ${TEST_APP}.global1.dokku.me - assert_output_contains ${TEST_APP}.global2.dokku.me + assert_output_contains "${TEST_APP}.global1.${DOKKU_DOMAIN}" + assert_output_contains "${TEST_APP}.global2.${DOKKU_DOMAIN}" } @test "(domains) domains:set-global" { - run /bin/bash -c "dokku domains:add-global global1.dokku.me global2.dokku.me" + run /bin/bash -c "dokku domains:add-global global1.${DOKKU_DOMAIN} global2.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:set-global global3.dokku.me global4.dokku.me" + run /bin/bash -c "dokku domains:set-global global3.${DOKKU_DOMAIN} global4.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - dokku domains:setup $TEST_APP + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success + + run create_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku domains:report $TEST_APP 2>/dev/null" echo "output: $output" echo "status: $status" assert_success - assert_output_contains ${TEST_APP}.global1.dokku.me 0 - assert_output_contains ${TEST_APP}.global2.dokku.me 0 - assert_output_contains ${TEST_APP}.global3.dokku.me - assert_output_contains ${TEST_APP}.global4.dokku.me + assert_output_contains "${TEST_APP}.global1.${DOKKU_DOMAIN}" 0 + assert_output_contains "${TEST_APP}.global2.${DOKKU_DOMAIN}" 0 + assert_output_contains "${TEST_APP}.global3.${DOKKU_DOMAIN}" + assert_output_contains "${TEST_APP}.global4.${DOKKU_DOMAIN}" } @test "(domains) app name overlaps with global domain.tld" { @@ -315,9 +373,16 @@ teardown() { echo "status: $status" assert_success - # run domains:clear in order to invoke default vhost creation - dokku --quiet apps:create test.dokku.test - dokku --quiet domains:clear test.dokku.test + run /bin/bash -c "dokku --quiet apps:create test.dokku.test" + echo "output: $output" + echo "status: $status" + assert_success + + # run domains:reset in order to invoke default vhost creation + run /bin/bash -c "dokku --quiet domains:reset test.dokku.test" + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku domains:report test.dokku.test --domains-app-vhosts" echo "output: $output" @@ -325,5 +390,132 @@ teardown() { assert_success assert_output "test.dokku.test" - dokku --force apps:destroy test.dokku.test + run /bin/bash -c "dokku --force apps:destroy test.dokku.test" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(domains) app rename only renames domains associated with global domains" { + run /bin/bash -c "dokku domains:set-global ${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.${DOKKU_DOMAIN} $TEST_APP.dokku.test" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku apps:rename $TEST_APP other-name" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:report other-name --domains-app-vhosts" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "other-name.${DOKKU_DOMAIN} $TEST_APP.dokku.test" + + run /bin/bash -c "dokku apps:rename other-name $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(domains) app rename preserves all subdomains across multiple global vhosts" { + run /bin/bash -c "dokku domains:set-global ${DOKKU_DOMAIN} dokku.test" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.${DOKKU_DOMAIN} $TEST_APP.dokku.test custom.example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku apps:rename $TEST_APP other-name" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:report other-name --domains-app-vhosts" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "other-name.${DOKKU_DOMAIN} other-name.dokku.test custom.example.com" + + run /bin/bash -c "dokku apps:rename other-name $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(domains) app rename preserves custom domains when no global vhost is set" { + run /bin/bash -c "dokku domains:clear-global" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP custom.example.com other.example.org" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku apps:rename $TEST_APP other-name" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:report other-name --domains-app-vhosts" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "custom.example.com other.example.org" + + run /bin/bash -c "dokku apps:rename other-name $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(domains) verify warning on ipv4/ipv6 domain name" { + touch /etc/nginx/sites-enabled/default + rm "$DOKKU_ROOT/VHOST" + echo "127.0.0.1" >"$DOKKU_ROOT/VHOST" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Detected IPv4 domain name with nginx proxy enabled." + + rm -f /etc/nginx/sites-enabled/default + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Detected IPv4 domain name with nginx proxy enabled." 0 +} + +@test "(domains) https:443" { + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP cockpithq/cockpit:core-latest" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "http://$TEST_APP.${DOKKU_DOMAIN}" 2 + assert_output_contains "http://$TEST_APP.${DOKKU_DOMAIN}:2019" + assert_output_contains "udp://$TEST_APP.${DOKKU_DOMAIN}:443" + assert_output_contains "http://$TEST_APP.${DOKKU_DOMAIN}:443" 0 + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:2019:2019 http:80:80 https:443:443 udp:443:443" } diff --git a/tests/unit/events.bats b/tests/unit/events.bats index 65a3cc7e6ad..ab80d2e94e0 100644 --- a/tests/unit/events.bats +++ b/tests/unit/events.bats @@ -24,14 +24,17 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "test -f /etc/rsyslog.d/99-dokku.conf" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "stat -c '%U:%G:%a' /var/log/dokku/" echo "output: $output" echo "status: $status" assert_output "syslog:dokku:775" + run /bin/bash -c "stat -c '%U:%G:%a' /var/log/dokku/events.log" echo "output: $output" echo "status: $status" @@ -40,10 +43,22 @@ teardown() { @test "(events) log commands" { run /bin/bash -c "dokku events:on" - deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku events" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku events:off" + echo "output: $output" + echo "status: $status" + assert_success } diff --git a/tests/unit/git_1.bats b/tests/unit/git_1.bats index 760b90f6f33..46410f0c95a 100644 --- a/tests/unit/git_1.bats +++ b/tests/unit/git_1.bats @@ -12,6 +12,229 @@ teardown() { global_teardown } +@test "(git:report) info-flag works before deploy" { + run /bin/bash -c "dokku git:report $TEST_APP --git-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:set $TEST_APP deploy-branch main" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "main" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "main" + + run /bin/bash -c "dokku git:set $TEST_APP deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:report $TEST_APP --git-sha" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(git:report) keep-git-dir raw vs computed vs global" { + run /bin/bash -c "dokku git:set --global keep-git-dir" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-keep-git-dir" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku git:report $TEST_APP --git-global-keep-git-dir" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-keep-git-dir" + assert_success + assert_output "false" + + run /bin/bash -c "dokku git:set --global keep-git-dir true" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-global-keep-git-dir" + assert_success + assert_output "true" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-keep-git-dir" + assert_success + assert_output "true" + + run /bin/bash -c "dokku git:set $TEST_APP keep-git-dir false" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-keep-git-dir" + assert_success + assert_output "false" + + run /bin/bash -c "dokku git:report $TEST_APP --git-global-keep-git-dir" + assert_success + assert_output "true" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-keep-git-dir" + assert_success + assert_output "false" + + run /bin/bash -c "dokku git:set $TEST_APP keep-git-dir" + assert_success + + run /bin/bash -c "dokku git:set --global keep-git-dir" + assert_success +} + +@test "(git:report) rev-env-var raw" { + # Unset the per-app property file if a previous test left it written-empty + # (git:set rev-env-var "" intentionally writes an empty file as the + # documented "unset" behavior, which masks the report's default fallback). + run /bin/bash -c "dokku git:set $TEST_APP rev-env-var COMMIT_SHA" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-rev-env-var" + assert_success + assert_output "COMMIT_SHA" + + run /bin/bash -c "dokku git:set $TEST_APP rev-env-var" + assert_success +} + +@test "(git:report) source-image raw" { + run /bin/bash -c "dokku git:report $TEST_APP --git-source-image" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku git:set $TEST_APP source-image alpine:3.20" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-source-image" + assert_success + assert_output "alpine:3.20" + + run /bin/bash -c "dokku git:set $TEST_APP source-image" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-source-image" + assert_success + assert_output_not_exists +} + +@test "(git:report) raw and computed keys in --format json" { + run /bin/bash -c "dokku git:report $TEST_APP --format json | jq -r '.\"deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report $TEST_APP --format json | jq -r '.\"computed-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:report $TEST_APP --format json | jq -r '.\"keep-git-dir\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report $TEST_APP --format json | jq -r '.\"computed-keep-git-dir\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" +} + +@test "(git:report) --global raw and computed keys" { + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"global-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"computed-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"global-archive-max-files\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"computed-archive-max-files\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "10000" + + run /bin/bash -c "dokku git:set --global deploy-branch main" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"global-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "main" + + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"computed-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "main" + + run /bin/bash -c "dokku git:set --global deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"global-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" +} + @test "(git) git:help" { run /bin/bash -c "dokku git" echo "output: $output" @@ -52,7 +275,7 @@ teardown() { run /bin/bash -c "dokku config:get $TEST_APP GIT_REV" echo "output: $output" echo "status: $status" - assert_output "" + assert_output_not_exists } @test "(git) customize the GIT_REV environment variable" { @@ -83,11 +306,15 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku enter $TEST_APP web git status" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku enter $TEST_APP web ls .git" echo "output: $output" echo "status: $status" assert_success - assert_output_contains "branches" assert_output_contains "config" assert_output_contains "description" assert_output_contains "HEAD" diff --git a/tests/unit/git_2.bats b/tests/unit/git_2.bats index 8a65f2ab61a..4e0d1ef2c3a 100644 --- a/tests/unit/git_2.bats +++ b/tests/unit/git_2.bats @@ -12,6 +12,29 @@ teardown() { global_teardown } +@test "(git) push to non-deploy branch" { + run /bin/bash -c "dokku git:set $TEST_APP deploy-branch custom-branch" + echo "output: $output" + echo "status: $status" + assert_success + + GIT_REMOTE_BRANCH=custom-branch run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:set $TEST_APP deploy-branch non-existing-branch" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Cannot build from 'non-existing-branch' as there are no commits stored at that branch." + assert_failure +} + @test "(git) deploy specific branch" { run /bin/bash -c "dokku git:set --global deploy-branch global-branch" echo "output: $output" @@ -71,7 +94,10 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "test -d $DOKKU_ROOT/$TEST_APP/refs" echo "output: $output" diff --git a/tests/unit/git_3.bats b/tests/unit/git_3.bats index 50a624e5d3f..069816cd45d 100644 --- a/tests/unit/git_3.bats +++ b/tests/unit/git_3.bats @@ -2,6 +2,12 @@ load test_helper +SMOKE_TEST_APP_1_0_0_SHA=9cf71bba639c4f1671dfd42685338b762d3354f2 +SMOKE_TEST_APP_2_0_0_SHA=5c8a5e42bbd7fae98bd657fb17f41c6019b303f9 +SMOKE_TEST_APP_ANOTHER_BRANCH_SHA=5c8a5e42bbd7fae98bd657fb17f41c6019b303f9 +SMOKE_TEST_APP_MASTER_SHA=96f6ba2db912c93707080a358016d1febb7c560a +SMOKE_TEST_APP_COMMIT_SHA=5c8a5e42bbd7fae98bd657fb17f41c6019b303f9 + setup() { global_setup create_app @@ -87,6 +93,145 @@ teardown() { echo "status: $status" assert_success assert_output_contains "Setting netrc auth entry for host github.com" + + run /bin/bash -c "printf 'piped-password' | dokku git:auth github.com piped-username" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting netrc auth entry for host github.com" + + run /bin/bash -c "netrc get --netrc-file ${DOKKU_ROOT}/.netrc github.com" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "piped-username:piped-password" + + run /bin/bash -c "stat -c '%a' ${DOKKU_ROOT}/.netrc" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "600" + + run /bin/bash -c "stat -c '%U:%G' ${DOKKU_ROOT}/.netrc" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku:dokku" +} + +@test "(git) git:auth repairs world-readable .netrc" { + run /bin/bash -c "chmod 644 ${DOKKU_ROOT}/.netrc" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "stat -c '%a' ${DOKKU_ROOT}/.netrc" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "644" + + run /bin/bash -c "dokku git:auth github.com username password" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting netrc auth entry for host github.com" + + run /bin/bash -c "stat -c '%a' ${DOKKU_ROOT}/.netrc" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "600" + + run /bin/bash -c "stat -c '%U:%G' ${DOKKU_ROOT}/.netrc" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku:dokku" +} + +@test "(git) git:auth unset preserves 0600 on remaining .netrc" { + run /bin/bash -c "dokku git:auth github.com username password" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:auth other.example.com user2 pass2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "chmod 644 ${DOKKU_ROOT}/.netrc" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:auth github.com" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Removing netrc auth entry for host github.com" + + if [[ -f "${DOKKU_ROOT}/.netrc" ]]; then + run /bin/bash -c "stat -c '%a' ${DOKKU_ROOT}/.netrc" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "600" + fi +} + +@test "(git) git:auth-status" { + run /bin/bash -c "dokku git:auth-status" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku git:auth-status github.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:auth github.com username password" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:auth-status github.com" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku git:auth-status github.com username password" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:auth-status github.com username wrong-password" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku git:auth-status github.com other-username password" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku git:auth-status github.com username" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Missing password for netrc auth entry" + + run /bin/bash -c "printf 'password' | dokku git:auth-status github.com username" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "printf 'wrong-password' | dokku git:auth-status github.com username" + echo "output: $output" + echo "status: $status" + assert_failure } @test "(git) git:sync new [errors]" { @@ -121,6 +266,12 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_MASTER_SHA" } @test "(git) git:sync new [--no-build branch]" { @@ -128,6 +279,12 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_ANOTHER_BRANCH_SHA" } @test "(git) git:sync new [--no-build tag]" { @@ -135,13 +292,25 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" } @test "(git) git:sync new [--no-build commit]" { - run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git 5c8a5e42bbd7fae98bd657fb17f41c6019b303f9" + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git $SMOKE_TEST_APP_COMMIT_SHA" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_COMMIT_SHA" } @test "(git) git:sync new [--build noarg]" { @@ -150,6 +319,29 @@ teardown() { echo "status: $status" assert_success assert_output_contains "Application deployed" + + run /bin/bash -c "dokku config:get $TEST_APP GIT_REV" + echo "output: $output" + echo "status: $status" + assert_output_exists + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_MASTER_SHA" + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app.git" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_MASTER_SHA" } @test "(git) git:sync new [--build branch]" { @@ -158,6 +350,24 @@ teardown() { echo "status: $status" assert_success assert_output_contains "Application deployed" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_ANOTHER_BRANCH_SHA" + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_ANOTHER_BRANCH_SHA" } @test "(git) git:sync new [--build tag]" { @@ -166,14 +376,50 @@ teardown() { echo "status: $status" assert_success assert_output_contains "Application deployed" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app.git 1.0.0" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" } @test "(git) git:sync new [--build commit]" { - run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git 5c8a5e42bbd7fae98bd657fb17f41c6019b303f9" + run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git $SMOKE_TEST_APP_COMMIT_SHA" echo "output: $output" echo "status: $status" assert_success assert_output_contains "Application deployed" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_COMMIT_SHA" + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app.git $SMOKE_TEST_APP_COMMIT_SHA" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_COMMIT_SHA" } @test "(git) git:sync existing [errors]" { @@ -204,15 +450,28 @@ teardown() { } @test "(git) git:sync existing [--no-build noarg]" { - run /bin/bash -c "dokku --trace git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git 1.0.0" + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git 1.0.0" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku --trace git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git" + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" echo "output: $output" echo "status: $status" assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Fetching remote code for" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_MASTER_SHA" } @test "(git) git:sync existing [--no-build branch]" { @@ -221,10 +480,22 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_ANOTHER_BRANCH_SHA" } @test "(git) git:sync existing [--no-build tag]" { @@ -233,10 +504,46 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git 2.0.0" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_2_0_0_SHA" +} + +@test "(git) git:sync existing [--no-build annotated-tag]" { + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git 1.0.0" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git 2.0.0-annotated" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_2_0_0_SHA" } @test "(git) git:sync existing [--no-build commit]" { @@ -245,10 +552,22 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git 5c8a5e42bbd7fae98bd657fb17f41c6019b303f9" + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git $SMOKE_TEST_APP_COMMIT_SHA" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_COMMIT_SHA" } @test "(git) git:sync existing [--build noarg]" { @@ -257,11 +576,35 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git" echo "output: $output" echo "status: $status" assert_success assert_output_contains "Application deployed" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_MASTER_SHA" + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app.git" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_MASTER_SHA" } @test "(git) git:sync existing [--build branch]" { @@ -270,11 +613,35 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_2_0_0_SHA" + run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" echo "output: $output" echo "status: $status" assert_success assert_output_contains "Application deployed" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_ANOTHER_BRANCH_SHA" + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_ANOTHER_BRANCH_SHA" } @test "(git) git:sync existing [--build tag]" { @@ -283,11 +650,35 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git 2.0.0" echo "output: $output" echo "status: $status" assert_success assert_output_contains "Application deployed" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_2_0_0_SHA" + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app.git 2.0.0" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_2_0_0_SHA" } @test "(git) git:sync existing [--build commit]" { @@ -296,13 +687,36 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git 5c8a5e42bbd7fae98bd657fb17f41c6019b303f9" + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_1_0_0_SHA" + + run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git $SMOKE_TEST_APP_COMMIT_SHA" echo "output: $output" echo "status: $status" assert_success assert_output_contains "Application deployed" -} + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_COMMIT_SHA" + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app.git $SMOKE_TEST_APP_COMMIT_SHA" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "cat /home/dokku/$TEST_APP/refs/heads/master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$SMOKE_TEST_APP_COMMIT_SHA" +} @test "(git) git:sync private" { if [[ -z "$SYNC_GITHUB_USERNAME" ]] || [[ -z "$SYNC_GITHUB_PASSWORD" ]]; then @@ -329,6 +743,12 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "dokku git:sync --build-if-changes $TEST_APP https://github.com/dokku/smoke-test-app-private.git" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" } @test "(git) git:public-key" { @@ -348,3 +768,192 @@ teardown() { echo "status: $status" assert_success } + +@test "(git:sync) --skip-deploy-branch" { + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:sync $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Detected branch, setting deploy-branch to another-branch" + + run /bin/bash -c "dokku git:report $TEST_APP --git-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "another-branch" + + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success + + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:sync --skip-deploy-branch $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "skipping deploy-branch setting" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" +} + +@test "(git:sync) --build --skip-deploy-branch" { + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:sync --build --skip-deploy-branch $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "skipping deploy-branch setting" + assert_output_contains "Application deployed" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" +} + +@test "(git:sync) --build-if-changes --skip-deploy-branch" { + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:sync --build-if-changes --skip-deploy-branch $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "skipping deploy-branch setting" + assert_output_contains "Application deployed" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:sync --build-if-changes --skip-deploy-branch $TEST_APP https://github.com/dokku/smoke-test-app.git another-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping build as no changes were detected" + + run /bin/bash -c "dokku git:report $TEST_APP --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" +} + +@test "(git:report) --format json" { + run /bin/bash -c "dokku git:set $TEST_APP deploy-branch main" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --format json | jq -r '.\"deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "main" + + run /bin/bash -c "dokku git:report $TEST_APP --format json | jq -r '.\"global-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report $TEST_APP --format json | jq -r '.\"computed-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "main" + + run /bin/bash -c "dokku git:report $TEST_APP --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report $TEST_APP --format json --git-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "--format flag cannot be specified when specifying an info flag" +} + +@test "(git:report) --global" { + run /bin/bash -c "dokku git:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global git information" + assert_output_contains "Git global deploy branch" +} + +@test "(git:report) --global --format json" { + run /bin/bash -c "dokku git:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"global-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report --global --format json | jq -r '.\"computed-deploy-branch\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" + + run /bin/bash -c "dokku git:report --global --format json | jq -r 'has(\"deploy-branch\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku git:report --global --git-global-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku git:report --global --git-computed-deploy-branch" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "master" +} diff --git a/tests/unit/git_4.bats b/tests/unit/git_4.bats index 6d26de148d9..3abdc460bf8 100644 --- a/tests/unit/git_4.bats +++ b/tests/unit/git_4.bats @@ -23,10 +23,56 @@ teardown() { } @test "(git) git:from-image [normal]" { - run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.5.1-ls1" + run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.6.21" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/HEAD" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ref: refs/heads/master" +} + +@test "(git) git:from-image --force" { + run /bin/bash -c "docker image rm linuxserver/foldingathome:7.6.21 2>/dev/null || true" + + run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Pulling image" + + run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_output_contains "Image exists on host, skipping pull" + assert_output_contains "No changes detected, skipping git commit" + + run /bin/bash -c "dokku git:from-image --force $TEST_APP linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_output_contains "Force pulling image" + assert_output_contains "No changes detected, skipping git commit" +} + +@test "(git) git:from-image [normal-custom-branch]" { + run /bin/bash -c "dokku git:set $TEST_APP deploy-branch main" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /home/dokku/$TEST_APP/HEAD" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ref: refs/heads/main" } @test "(git) git:from-image [normal-git-init]" { @@ -45,7 +91,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.5.1-ls1" + run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.6.21" echo "output: $output" echo "status: $status" assert_success @@ -58,8 +104,82 @@ teardown() { assert_success } +@test "(git) git:from-image [failing deploy]" { + local CUSTOM_TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$CUSTOM_TMP"' INT TERM + rmdir "$CUSTOM_TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/python" "$CUSTOM_TMP" + + run /bin/bash -c "docker image build -t dokku-test/$TEST_APP:latest -f $CUSTOM_TMP/alt.Dockerfile $CUSTOM_TMP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image build -t dokku-test/$TEST_APP:v2 --build-arg BUILD_ARG=value -f $CUSTOM_TMP/alt.Dockerfile $CUSTOM_TMP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP FAIL_ON_STARTUP=true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP dokku-test/$TEST_APP:latest" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku config:get $TEST_APP FAIL_ON_STARTUP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku git:status $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output "fatal: this operation must be run in a work tree" + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP FAIL_ON_STARTUP=false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP dokku-test/$TEST_APP:latest" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP FAIL_ON_STARTUP=true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP dokku-test/$TEST_APP:v2" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP FAIL_ON_STARTUP=false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP dokku-test/$TEST_APP:v2" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "No changes detected, skipping git commit" 0 + + run /bin/bash -c "docker image rm dokku-test/$TEST_APP:latest dokku-test/$TEST_APP:v2" + echo "output: $output" + echo "status: $status" + assert_success +} + @test "(git) git:from-image [onbuild]" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM run /bin/bash -c "dokku storage:mount $TEST_APP /var/run/docker.sock:/var/run/docker.sock" @@ -118,7 +238,7 @@ EOF } @test "(git) git:from-image labels correctly" { - run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.5.1-ls1" + run /bin/bash -c "dokku git:from-image $TEST_APP linuxserver/foldingathome:7.6.21" echo "output: $output" echo "status: $status" assert_success @@ -127,5 +247,5 @@ EOF echo "output: $output" echo "status: $status" assert_success - assert_output_contains "linuxserver/foldingathome:7.5.1-ls1" + assert_output_contains "linuxserver/foldingathome:7.6.21" } diff --git a/tests/unit/git_5.bats b/tests/unit/git_5.bats index 016eab882f0..be7ef97bd68 100644 --- a/tests/unit/git_5.bats +++ b/tests/unit/git_5.bats @@ -34,7 +34,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(git) git:from-archive [tar.gz]" { @@ -42,7 +42,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(git) git:from-archive [zip]" { @@ -50,7 +50,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(git) git:from-archive [stdin-file]" { @@ -63,7 +63,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } @test "(git) git:from-archive [stdin-curl]" { @@ -71,5 +71,5 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success http://${TEST_APP}.dokku.me + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } diff --git a/tests/unit/git_6.bats b/tests/unit/git_6.bats index ab706a5753d..03c81f264e0 100644 --- a/tests/unit/git_6.bats +++ b/tests/unit/git_6.bats @@ -4,27 +4,286 @@ load test_helper setup() { global_setup + create_app touch /home/dokku/.ssh/known_hosts chown dokku:dokku /home/dokku/.ssh/known_hosts - mkdir -p "$DOKKU_LIB_ROOT/data/git/$TEST_APP" - chown dokku:dokku "$DOKKU_LIB_ROOT/data/git/$TEST_APP" } teardown() { + docker image rm linuxserver/foldingathome:7.6.21 || true + docker image rm dokku/node-js-getting-started:latest || true + docker image rm dokku-test/$TEST_APP:latest || true + docker image rm dokku-test/$TEST_APP:v2 || true + docker image rm gliderlabs/logspout:v3.2.13 || true + rm -f /tmp/image.tar /tmp/image-2.tar rm -f /home/dokku/.ssh/id_rsa.pub || true + destroy_app global_teardown } -@test "(git) git:unlock [success]" { - run /bin/bash -c "dokku git:unlock $TEST_APP --force" +@test "(git) git:load-image [normal]" { + run /bin/bash -c "docker image pull linuxserver/foldingathome:7.6.21" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "docker image save -o /tmp/image.tar linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image rm linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image $TEST_APP linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(git) git:load-image [normal-git-init]" { + run rm -rf "/home/dokku/$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run mkdir "/home/dokku/$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run chown -R dokku:dokku "/home/dokku/$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image pull linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image save -o /tmp/image.tar linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image rm linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image $TEST_APP linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(git) git:load-image [normal-cnb]" { + run /bin/bash -c "docker image pull dokku/node-js-getting-started:latest" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image save -o /tmp/image.tar dokku/node-js-getting-started:latest" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image rm dokku/node-js-getting-started:latest" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image $TEST_APP dokku/node-js-getting-started:latest" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(git) git:load-image [failing deploy]" { + local CUSTOM_TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$CUSTOM_TMP"' INT TERM + rmdir "$CUSTOM_TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/python" "$CUSTOM_TMP" + + run /bin/bash -c "docker image build -t dokku-test/$TEST_APP:latest -f $CUSTOM_TMP/alt.Dockerfile $CUSTOM_TMP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image build -t dokku-test/$TEST_APP:v2 --build-arg BUILD_ARG=value -f $CUSTOM_TMP/alt.Dockerfile $CUSTOM_TMP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image save -o /tmp/image.tar dokku-test/$TEST_APP:latest" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image save -o /tmp/image-2.tar dokku-test/$TEST_APP:v2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image rm dokku-test/$TEST_APP:latest dokku-test/$TEST_APP:v2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP FAIL_ON_STARTUP=true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image $TEST_APP dokku-test/$TEST_APP:latest" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku config:get $TEST_APP FAIL_ON_STARTUP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku git:status $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output "fatal: this operation must be run in a work tree" + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP FAIL_ON_STARTUP=false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image $TEST_APP dokku-test/$TEST_APP:latest" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP FAIL_ON_STARTUP=true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image-2.tar | dokku git:load-image $TEST_APP dokku-test/$TEST_APP:v2" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP FAIL_ON_STARTUP=false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image-2.tar | dokku git:load-image $TEST_APP dokku-test/$TEST_APP:v2" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "No changes detected, skipping git commit" 0 } -@test "(git) git:unlock [missing arg]" { - run /bin/bash -c "dokku git:unlock" +@test "(git) git:load-image [onbuild]" { + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM + + run /bin/bash -c "dokku storage:mount $TEST_APP /var/run/docker.sock:/var/run/docker.sock" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image pull gliderlabs/logspout:v3.2.13" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image save -o /tmp/image.tar gliderlabs/logspout:v3.2.13" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image rm gliderlabs/logspout:v3.2.13" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image $TEST_APP gliderlabs/logspout:v3.2.13" + echo "output: $output" + echo "status: $status" + assert_failure + + cat <"$TMP/build.sh" +#!/bin/sh +set -e +apk add --update go build-base git mercurial ca-certificates +cd /src +go build -ldflags "-X main.Version=\$1" -o /bin/logspout +apk del go git mercurial build-base +rm -rf /root/go /var/cache/apk/* + +# backwards compatibility +ln -fs /tmp/docker.sock /var/run/docker.sock +EOF + + cat <"$TMP/modules.go" +package main + +import ( + _ "github.com/gliderlabs/logspout/adapters/multiline" + _ "github.com/gliderlabs/logspout/adapters/raw" + _ "github.com/gliderlabs/logspout/adapters/syslog" + _ "github.com/gliderlabs/logspout/healthcheck" + _ "github.com/gliderlabs/logspout/httpstream" + _ "github.com/gliderlabs/logspout/routesapi" + _ "github.com/gliderlabs/logspout/transports/tcp" + _ "github.com/gliderlabs/logspout/transports/tls" + _ "github.com/gliderlabs/logspout/transports/udp" +) +EOF + + run sudo chown -R dokku:dokku "$TMP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image --build-dir $TMP $TEST_APP gliderlabs/logspout:v3.2.13" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image $TEST_APP gliderlabs/logspout:v3.2.13" echo "output: $output" echo "status: $status" assert_failure } + +@test "(git) git:load-image labels correctly" { + run /bin/bash -c "docker image pull linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image save -o /tmp/image.tar linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image rm linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "cat /tmp/image.tar | dokku git:load-image $TEST_APP linuxserver/foldingathome:7.6.21" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image inspect dokku/$TEST_APP:latest --format '{{ index .Config.Labels \"com.dokku.docker-image-labeler/alternate-tags\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "linuxserver/foldingathome:7.6.21" +} diff --git a/tests/unit/git_7.bats b/tests/unit/git_7.bats new file mode 100644 index 00000000000..be9ae88886d --- /dev/null +++ b/tests/unit/git_7.bats @@ -0,0 +1,51 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_PLUGIN_APP=smoke-test-app +TEST_PLUGIN_GIT_REPO=https://github.com/dokku/${TEST_PLUGIN_APP}.git +TEST_PLUGIN_LOCAL_REPO="$(mktemp -d)/$TEST_PLUGIN_APP" + +clone_test_app() { + git clone "$TEST_PLUGIN_GIT_REPO" "$TEST_PLUGIN_LOCAL_REPO" +} + +remove_test_app() { + rm -rf $TEST_PLUGIN_LOCAL_REPO +} + +setup() { + global_setup + create_app + clone_test_app +} + +teardown() { + remove_test_app || true + destroy_app + global_teardown +} + +@test "(git) push tags and branches" { + # https://github.com/dokku/dokku/issues/5188 + local GIT_REMOTE=${GIT_REMOTE:="dokku@${DOKKU_DOMAIN}:$TEST_APP"} + run git -C "$TEST_PLUGIN_LOCAL_REPO" remote add target "$GIT_REMOTE" + echo "output: $output" + echo "status: $status" + assert_success + + run git -C "$TEST_PLUGIN_LOCAL_REPO" push target 1.0.0:master + echo "output: $output" + echo "status: $status" + assert_success + + run git -C "$TEST_PLUGIN_LOCAL_REPO" push target 2.0.0:master -f + echo "output: $output" + echo "status: $status" + assert_success + + run git -C "$TEST_PLUGIN_LOCAL_REPO" push target master -f + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/tests/unit/haproxy-vhosts.bats b/tests/unit/haproxy-vhosts.bats new file mode 100644 index 00000000000..1c5a5f97004 --- /dev/null +++ b/tests/unit/haproxy-vhosts.bats @@ -0,0 +1,76 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + create_app +} + +teardown() { + destroy_app + dokku haproxy:set --global log-level >/dev/null 2>&1 || true + dokku haproxy:set --global refresh-conf >/dev/null 2>&1 || true +} + +@test "(haproxy:report) info-flag works before deploy" { + run /bin/bash -c "dokku haproxy:set --global refresh-conf" + assert_success + + run /bin/bash -c "dokku haproxy:report $TEST_APP --haproxy-computed-refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "10" + + run /bin/bash -c "dokku haproxy:report $TEST_APP --haproxy-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(haproxy:report) --format json" { + run /bin/bash -c "dokku haproxy:set --global log-level" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --format json | jq -r '.\"computed-log-level\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" + + run /bin/bash -c "dokku haproxy:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global haproxy information" +} + +@test "(haproxy:report) refresh-conf global computed" { + run /bin/bash -c "dokku haproxy:set --global refresh-conf" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --haproxy-global-refresh-conf" + assert_success + assert_output "" + + run /bin/bash -c "dokku haproxy:report --global --haproxy-computed-refresh-conf" + assert_success + assert_output "10" + + run /bin/bash -c "dokku haproxy:set --global refresh-conf 30" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --haproxy-global-refresh-conf" + assert_success + assert_output "30" + + run /bin/bash -c "dokku haproxy:report --global --haproxy-computed-refresh-conf" + assert_success + assert_output "30" +} diff --git a/tests/unit/haproxy.bats b/tests/unit/haproxy.bats new file mode 100644 index 00000000000..6edc97db837 --- /dev/null +++ b/tests/unit/haproxy.bats @@ -0,0 +1,327 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku nginx:stop + dokku haproxy:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory + dokku haproxy:set --global letsencrypt-email + dokku haproxy:set --global refresh-conf 2 + dokku haproxy:start + create_app +} + +teardown() { + global_teardown + destroy_app + dokku haproxy:stop + dokku haproxy:set --global refresh-conf + dokku nginx:start +} + +@test "(haproxy) haproxy:help" { + run /bin/bash -c "dokku haproxy" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the haproxy proxy integration" + help_output="$output" + + run /bin/bash -c "dokku haproxy:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the haproxy proxy integration" + assert_output "$help_output" +} + +@test "(haproxy) global-only keys" { + for key in image log-level letsencrypt-email letsencrypt-server refresh-conf; do + run /bin/bash -c "dokku haproxy:set $TEST_APP $key somevalue" + echo "key: $key" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" + done +} + +@test "(haproxy) refresh-conf" { + run /bin/bash -c "dokku haproxy:set $TEST_APP refresh-conf 2" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" + + run /bin/bash -c "dokku haproxy:set --global refresh-conf 5" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --haproxy-global-refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5" + + run /bin/bash -c "dokku haproxy:report --global --haproxy-computed-refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5" + + run /bin/bash -c "dokku haproxy:show-config" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "EASYHAPROXY_REFRESH_CONF=5" + + run /bin/bash -c "dokku haproxy:set --global refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --haproxy-global-refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku haproxy:report --global --haproxy-computed-refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "10" +} + +@test "(haproxy:report) --global raw and computed keys in --format json" { + run /bin/bash -c "dokku haproxy:set --global refresh-conf" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --format json | jq -r '.\"global-image\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku haproxy:report --global --format json | jq -r '.\"computed-image\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku haproxy:report --global --format json | jq -r '.\"global-refresh-conf\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku haproxy:report --global --format json | jq -r '.\"computed-refresh-conf\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "10" +} + +@test "(haproxy) log-level" { + run /bin/bash -c "dokku haproxy:set --global log-level DEBUG" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:start" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(haproxy) single domain" { + run /bin/bash -c "dokku proxy:set $TEST_APP haproxy" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response_contains "http" "$TEST_APP.$DOKKU_DOMAIN" "80" "/" "python/http.server" +} + +@test "(haproxy) multiple domains" { + run /bin/bash -c "dokku proxy:set $TEST_APP haproxy" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker logs haproxy-haproxy-1" + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response "http" "$TEST_APP.$DOKKU_DOMAIN" "80" "/" "python/http.server" + assert_http_localhost_response "http" "$TEST_APP-2.$DOKKU_DOMAIN" "80" "/" "python/http.server" +} + +@test "(haproxy) ssl" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP haproxy" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"haproxy.$TEST_APP-web.redirect_ssl\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku haproxy:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"haproxy.$TEST_APP-web.redirect_ssl\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" +} + +@test "(haproxy) label management" { + run /bin/bash -c "dokku proxy:set $TEST_APP haproxy" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:labels:add $TEST_APP haproxy.directive value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:labels:show $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "haproxy.directive=value" + + run /bin/bash -c "dokku haproxy:labels:show $TEST_APP haproxy.directive" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "value" + + run /bin/bash -c "dokku haproxy:labels:show $TEST_APP haproxy.directive2" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"haproxy.directive\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "value" + + run /bin/bash -c "dokku haproxy:labels:remove $TEST_APP haproxy.directive" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:labels:show $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "haproxy.directive=value" + + run /bin/bash -c "dokku haproxy:labels:show $TEST_APP haproxy.directive" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"haproxy.directive\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} diff --git a/tests/unit/init.bats b/tests/unit/init.bats index 5455e062602..c432a934cc2 100644 --- a/tests/unit/init.bats +++ b/tests/unit/init.bats @@ -15,32 +15,47 @@ teardown() { @test "(init) buildpack" { source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" local APP="zombies-buildpack" - deploy_app "$APP" + run deploy_app "$APP" + echo "output: $output" + echo "status: $status" + assert_success + local CIDS=$(get_app_container_ids "$APP") - run "$DOCKER_BIN" container top "$CIDS" + run /bin/bash -c "$DOCKER_BIN container top $CIDS" echo "output: $output" - assert_output_contains "" "0" + echo "status: $status" + assert_output_not_contains "" } @test "(init) dockerfile no tini" { source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" local APP="zombies-dockerfile-no-tini" - deploy_app "$APP" + run deploy_app "$APP" + echo "output: $output" + echo "status: $status" + assert_success + local CIDS=$(get_app_container_ids "$APP") - run "$DOCKER_BIN" container top "$CIDS" + run /bin/bash -c "$DOCKER_BIN container top $CIDS" echo "output: $output" - assert_output_contains "" "0" + echo "status: $status" + assert_output_not_contains "" } @test "(init) dockerfile with tini" { source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" local APP="zombies-dockerfile-tini" - deploy_app "$APP" + run deploy_app "$APP" + echo "output: $output" + echo "status: $status" + assert_success + local CIDS=$(get_app_container_ids "$APP") - run "$DOCKER_BIN" container top "$CIDS" + run /bin/bash -c "$DOCKER_BIN container top $CIDS" echo "output: $output" - assert_output_contains "" "0" + echo "status: $status" + assert_output_not_contains "" } diff --git a/tests/unit/logs.bats b/tests/unit/logs.bats index 58af987c8e4..9acde93486f 100644 --- a/tests/unit/logs.bats +++ b/tests/unit/logs.bats @@ -3,11 +3,16 @@ load test_helper setup() { + rm "${BATS_PARENT_TMPNAME}.skip" || true global_setup } teardown() { destroy_app + dokku logs:set --global vector-image >/dev/null 2>/dev/null || true + dokku logs:set --global vector-networks >/dev/null 2>/dev/null || true + docker network rm test-vector-net-a >/dev/null || true + docker network rm test-vector-net-b >/dev/null || true global_teardown } @@ -53,7 +58,7 @@ teardown() { echo "status: $status" assert_failure assert_output_contains "$TEST_APP logs information" 0 - assert_output_contains "Invalid flag passed, valid flags: --logs-computed-max-size, --logs-global-max-size, --logs-global-vector-sink, --logs-max-size, --logs-vector-sink" + assert_output_contains "Invalid flag passed, valid flags: --logs-app-label-alias, --logs-computed-app-label-alias, --logs-computed-max-size, --logs-computed-vector-image, --logs-computed-vector-networks, --logs-computed-vector-sink, --logs-global-app-label-alias, --logs-global-max-size, --logs-global-vector-image, --logs-global-vector-networks, --logs-global-vector-sink, --logs-max-size, --logs-vector-sink" run /bin/bash -c "dokku logs:report $TEST_APP --logs-vector-sink 2>&1" echo "output: $output" @@ -70,6 +75,14 @@ teardown() { assert_output_contains "Invalid flag passed" 0 } +@test "(logs) logs:report --global invalid flag" { + run /bin/bash -c "dokku logs:report --global --invalid-flag 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed, valid flags: --logs-computed-app-label-alias, --logs-computed-max-size, --logs-computed-vector-image, --logs-computed-vector-networks, --logs-computed-vector-sink, --logs-global-app-label-alias, --logs-global-max-size, --logs-global-vector-image, --logs-global-vector-networks, --logs-global-vector-sink" +} + @test "(logs) logs:set [error]" { run /bin/bash -c "dokku logs:set 2>&1" echo "output: $output" @@ -98,13 +111,19 @@ teardown() { echo "output: $output" echo "status: $status" assert_failure - assert_output_contains "Invalid property specified, valid properties include: max-size, vector-sink" + assert_output_contains "Invalid property specified, valid properties include: app-label-alias, max-size, vector-image, vector-networks, vector-sink" run /bin/bash -c "dokku logs:set $TEST_APP invalid value" 2>&1 echo "output: $output" echo "status: $status" assert_failure - assert_output_contains "Invalid property specified, valid properties include: max-size, vector-sink" + assert_output_contains "Invalid property specified, valid properties include: app-label-alias, max-size, vector-image, vector-networks, vector-sink" + + run /bin/bash -c "dokku logs:set $TEST_APP vector-image timberio/vector:latest-debian 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "vector-image may only be set globally with --global" } @test "(logs) logs:set app" { @@ -181,7 +200,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output_not_exists run /bin/bash -c "dokku logs:report $TEST_APP --logs-max-size 2>&1" echo "output: $output" @@ -235,7 +254,50 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output_not_exists +} + +@test "(logs) logs:set equals in uri" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:set $TEST_APP vector-sink 'loki://?endpoint=https://host&encoding[codec]=text&auth[token]=foobar%3D&auth[strategy]=bearer'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting vector-sink" + assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" + # type: loki + # endpoint: https://host + # encoding[codec]: text + # auth[token]: foobar= + # auth[strategy]: bearer + + run /bin/bash -c "jq -r '.sinks[\"docker-sink:$TEST_APP\"].auth.strategy' /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "bearer" + + run /bin/bash -c "jq -r '.sinks[\"docker-sink:$TEST_APP\"].auth.token' /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "foobar=" + + run /bin/bash -c "jq -r '.sinks[\"docker-sink:$TEST_APP\"].endpoint' /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "https://host" + + run /bin/bash -c "jq -r '.sinks[\"docker-sink:$TEST_APP\"].encoding.codec' /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "text" } @test "(logs) logs:set escaped uri" { @@ -244,7 +306,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku logs:set $TEST_APP vector-sink http://?uri=https%3A//loggerservice.com%3A1234/%3Ftoken%3Dabc1234%26type%3Dvector" 2>&1 + run /bin/bash -c "dokku logs:set $TEST_APP vector-sink http://?uri=https%3A//loggerservice.com%3A1234/%3Ftoken%3Dabc1234%26type%3Dvector%26key%3Dvalue%2Bvalue2" echo "output: $output" echo "status: $status" assert_success @@ -255,13 +317,32 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "http://?uri=https%3A//loggerservice.com%3A1234/%3Ftoken%3Dabc1234%26type%3Dvector" + assert_output "http://?uri=https%3A//loggerservice.com%3A1234/%3Ftoken%3Dabc1234%26type%3Dvector%26key%3Dvalue%2Bvalue2" run /bin/bash -c "jq -r '.sinks[\"docker-sink:$TEST_APP\"].uri' /var/lib/dokku/data/logs/vector.json" echo "output: $output" echo "status: $status" assert_success - assert_output "https://loggerservice.com:1234/?token=abc1234&type=vector" + assert_output "https://loggerservice.com:1234/?token=abc1234&type=vector&key=value+value2" + + run /bin/bash -c "dokku logs:set $TEST_APP vector-sink 'aws_cloudwatch_logs://?create_missing_group=true&create_missing_stream=true&group_name=groupname&encoding[codec]=json®ion=sa-east-1&stream_name={{ host }}&auth[access_key_id]=KSDSIDJSAJD&auth[secret_access_key]=2932JSDJ%252BKSDSDJ'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting vector-sink" + assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-vector-sink 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "aws_cloudwatch_logs://?create_missing_group=true&create_missing_stream=true&group_name=groupname&encoding[codec]=json®ion=sa-east-1&stream_name={{ host }}&auth[access_key_id]=KSDSIDJSAJD&auth[secret_access_key]=2932JSDJ%252BKSDSDJ" + + run /bin/bash -c "jq -r '.sinks[\"docker-sink:$TEST_APP\"].auth.secret_access_key' /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2932JSDJ+KSDSDJ" } @test "(logs) logs:set global" { @@ -340,7 +421,7 @@ teardown() { assert_success assert_output_not_exists - run /bin/bash -c "dokku logs:report $TEST_APP --logs-global-max-size 2>&1" + run /bin/bash -c "dokku logs:report $TEST_APP --logs-computed-max-size 2>&1" echo "output: $output" echo "status: $status" assert_success @@ -356,6 +437,12 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-computed-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success assert_output "10m" run /bin/bash -c "dokku logs:set --global max-size 20m" 2>&1 @@ -370,6 +457,12 @@ teardown() { assert_success assert_output "20m" + run /bin/bash -c "dokku logs:report $TEST_APP --logs-computed-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "20m" + run /bin/bash -c "dokku logs:set --global max-size unlimited" 2>&1 echo "output: $output" echo "status: $status" @@ -392,10 +485,298 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-computed-max-size 2>&1" + echo "output: $output" + echo "status: $status" + assert_success assert_output "10m" } -@test "(logs) logs:set max-size with alternate log-driver daemon " { +@test "(logs:report) vector-sink raw vs global" { + run create_app + assert_success + + run /bin/bash -c "dokku logs:set --global vector-sink" + assert_success + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r '.\"logs-vector-sink\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r '.\"logs-global-vector-sink\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku logs:set --global vector-sink console://?encoding[codec]=json" + assert_success + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r '.\"logs-global-vector-sink\"'" + assert_success + assert_output "console://?encoding[codec]=json" + + run /bin/bash -c "dokku logs:set $TEST_APP vector-sink datadog_logs://?api_key=abc" + assert_success + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r '.\"logs-vector-sink\"'" + assert_success + assert_output "datadog_logs://?api_key=abc" + + run /bin/bash -c "dokku logs:set $TEST_APP vector-sink" + assert_success + + run /bin/bash -c "dokku logs:set --global vector-sink" + assert_success +} + +@test "(logs:report) global-vector-image and global-vector-networks raw" { + run create_app + assert_success + + run /bin/bash -c "dokku logs:set --global vector-image" + assert_success + + run /bin/bash -c "dokku logs:report --global --format json | jq -r '.\"logs-global-vector-image\"'" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku logs:report --global --format json | jq -r '.\"logs-computed-vector-image\"'" + assert_success + assert_output_exists + + run /bin/bash -c "dokku logs:set --global vector-image timberio/vector:custom" + assert_success + + run /bin/bash -c "dokku logs:report --global --format json | jq -r '.\"logs-global-vector-image\"'" + assert_success + assert_output "timberio/vector:custom" + + run /bin/bash -c "dokku logs:set --global vector-image" + assert_success + + run /bin/bash -c "dokku logs:report --global --format json | jq -r '.\"logs-global-vector-networks\"'" + assert_success + assert_output "" +} + +@test "(logs:report) does not expose deprecated logs-vector-global-* keys" { + run /bin/bash -c "dokku logs:report --global --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "logs-vector-global-image" + assert_output_not_contains "logs-vector-global-networks" +} + +@test "(logs) logs:set --global vector-networks" { + docker network create test-vector-net-a >/dev/null + docker network create test-vector-net-b >/dev/null + + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:set $TEST_APP vector-networks test-vector-net-a 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "vector-networks may only be set globally with --global" + + run /bin/bash -c "dokku logs:set --global vector-networks does-not-exist 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Network \"does-not-exist\" does not exist" + + run /bin/bash -c "dokku logs:set --global vector-networks bridge 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "\"bridge\" is not a valid entry for vector-networks" + + run /bin/bash -c "dokku logs:set --global vector-networks 'test-vector-net-a,' 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "empty entry in comma-separated list" + + run /bin/bash -c "dokku logs:set --global vector-networks test-vector-net-a 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting vector-networks" + + run /bin/bash -c "dokku logs:report --global --logs-global-vector-networks 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "test-vector-net-a" + + run /bin/bash -c "dokku logs:set --global vector-networks test-vector-net-a,test-vector-net-b 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting vector-networks" + + run /bin/bash -c "dokku logs:report --global --logs-global-vector-networks 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "test-vector-net-a,test-vector-net-b" + + run /bin/bash -c "dokku logs:set --global vector-networks 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting vector-networks" + + run /bin/bash -c "dokku logs:report --global --logs-global-vector-networks 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +@test "(logs) logs:vector-start attaches configured networks" { + docker network create test-vector-net-a >/dev/null + docker network create test-vector-net-b >/dev/null + + run /bin/bash -c "dokku logs:set --global vector-networks test-vector-net-a,test-vector-net-b 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:vector-start 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Vector container is running" + + run /bin/bash -c "sudo docker inspect --format='{{range \$k, \$v := .NetworkSettings.Networks}}{{\$k}} {{end}}' vector-vector-1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "test-vector-net-a" + assert_output_contains "test-vector-net-b" + assert_output_contains "bridge" 0 + + run /bin/bash -c "dokku logs:vector-stop 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:vector-start 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Vector container is running" + + run /bin/bash -c "sudo docker inspect --format='{{range \$k, \$v := .NetworkSettings.Networks}}{{\$k}} {{end}}' vector-vector-1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "test-vector-net-a" + assert_output_contains "test-vector-net-b" + assert_output_contains "bridge" 0 + + run /bin/bash -c "dokku logs:set --global vector-networks 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:vector-stop 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:vector-start 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sudo docker inspect --format='{{range \$k, \$v := .NetworkSettings.Networks}}{{\$k}} {{end}}' vector-vector-1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "bridge" + + run /bin/bash -c "dokku logs:vector-stop 2>&1" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(logs) logs:set app-label-alias" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku logs:set $TEST_APP vector-sink console://?encoding[codec]=json" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Setting vector-sink" + assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" + + run /bin/bash -c "dokku logs:set --global app-label-alias" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Unsetting app-label-alias" + assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" + + run /bin/bash -c "jq -r '.sources[\"docker-source:$TEST_APP\"].include_labels[0]' /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "com.dokku.app-name=$TEST_APP" + + run /bin/bash -c "dokku logs:set --global app-label-alias global-alt-name" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-computed-app-label-alias" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global-alt-name" + + run /bin/bash -c "cat /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "jq -r '.sources[\"docker-source:$TEST_APP\"].include_labels[0]' /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "global-alt-name=$TEST_APP" + + run /bin/bash -c "dokku logs:set --global app-label-alias alt-name" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Writing updated vector config to /var/lib/dokku/data/logs/vector.json" + + run /bin/bash -c "dokku logs:report $TEST_APP --logs-computed-app-label-alias" 2>&1 + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "alt-name" + + run /bin/bash -c "jq -r '.sources[\"docker-source:$TEST_APP\"].include_labels[0]' /var/lib/dokku/data/logs/vector.json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "alt-name=$TEST_APP" +} + +@test "(logs) logs:set max-size with alternate log-driver daemon" { if [[ "$REMOTE_CONTAINERS" == "true" ]]; then skip "skipping due non-existent docker service in remote dev container" fi @@ -405,7 +786,7 @@ teardown() { fi driver="$(jq -r '."log-driver"' /etc/docker/daemon.json)" - local TMP_FILE=$(mktemp "/tmp/dokku.me.XXXX") + local TMP_FILE=$(mktemp "/tmp/${DOKKU_DOMAIN}.XXXX") run create_app echo "output: $output" @@ -422,7 +803,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "--log-opt=max-size=20m" + assert_output "--log-opt=max-size=20m --restart=on-failure:10" DRIVER="journald" jq '."log-driver" = env.DRIVER' <"/etc/docker/daemon.json" >"$TMP_FILE" mv "$TMP_FILE" /etc/docker/daemon.json @@ -439,7 +820,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_not_exists + assert_output "--restart=on-failure:10" if [[ "$driver" = "null" ]]; then DRIVER="$driver" jq 'del(."log-driver")' <"/etc/docker/daemon.json" >"$TMP_FILE" @@ -460,7 +841,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "--log-opt=max-size=20m" + assert_output "--log-opt=max-size=20m --restart=on-failure:10" } @test "(logs) logs:set max-size with alternate log-driver" { @@ -479,7 +860,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "--log-opt=max-size=20m" + assert_output "--log-opt=max-size=20m --restart=on-failure:10" run /bin/bash -c "dokku docker-options:add $TEST_APP deploy --log-driver=local" 2>&1 echo "output: $output" @@ -490,7 +871,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "--log-opt=max-size=20m" + assert_output "--log-opt=max-size=20m --restart=on-failure:10" run /bin/bash -c "dokku docker-options:add $TEST_APP deploy --log-driver=json-file" 2>&1 echo "output: $output" @@ -501,18 +882,18 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output "--log-opt=max-size=20m" + assert_output "--log-opt=max-size=20m --restart=on-failure:10" run /bin/bash -c "dokku docker-options:add $TEST_APP deploy --log-driver=journald" 2>&1 echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "echo "" | dokku plugin:trigger docker-args-process-deploy $TEST_APP 2>&1" + run /bin/bash -c "echo "" | dokku plugin:trigger docker-args-process-deploy $TEST_APP 2>&1 | xargs" echo "output: $output" echo "status: $status" assert_success - assert_output "" + assert_output "--restart=on-failure:10" } @test "(logs) logs:vector" { @@ -522,18 +903,34 @@ teardown() { assert_failure assert_output_contains "Vector container does not exist" + run /bin/bash -c "dokku apps:create example.com" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku logs:vector-start 2>&1" echo "output: $output" echo "status: $status" assert_success assert_output_contains "Vector container is running" + run /bin/bash -c "sudo docker inspect --format='{{.HostConfig.RestartPolicy.Name}}' vector-vector-1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "unless-stopped" + run /bin/bash -c "dokku logs:vector-logs 2>&1" echo "output: $output" echo "status: $status" assert_success assert_output_contains "Vector container logs" + run /bin/bash -c "dokku --force apps:destroy example.com" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku logs:vector-logs --num 10 2>&1" echo "output: $output" echo "status: $status" @@ -550,7 +947,7 @@ teardown() { assert_output_contains "vector:" 5 assert_line_count 6 - run /bin/bash -c "docker stop vector" + run /bin/bash -c "docker stop vector-vector-1" echo "output: $output" echo "status: $status" assert_success @@ -566,5 +963,30 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "Stopping and removing vector container" + assert_output_contains "Stopping and removing vector container" +} + +@test "(logs:report) emits new stripped JSON keys alongside legacy" { + run create_app + assert_success + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r 'has(\"vector-sink\") and has(\"logs-vector-sink\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r 'has(\"global-vector-sink\") and has(\"logs-global-vector-sink\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r 'has(\"computed-vector-sink\") and has(\"logs-computed-vector-sink\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku logs:report $TEST_APP --format json | jq -r 'has(\"max-size\") and has(\"logs-max-size\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku logs:report --global --format json | jq -r 'has(\"global-vector-image\") and has(\"logs-global-vector-image\")'" + assert_success + assert_output "true" } diff --git a/tests/unit/network.bats b/tests/unit/network.bats index 338e26c1a19..449eefaccd4 100644 --- a/tests/unit/network.bats +++ b/tests/unit/network.bats @@ -9,11 +9,12 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" docker network rm create-network || true docker network rm deploy-network || true docker network rm initial-network || true + docker network rm "$TEST_NETWORK" || true global_teardown } @@ -31,9 +32,305 @@ teardown() { assert_output "$help_output" } +@test "(network:report) --global --format json" { + run /bin/bash -c "dokku network:set --global tld dokku.test" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-global-tld\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku.test" + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"global-tld\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku.test" + + run /bin/bash -c "dokku network:report --global --format json | jq -r 'has(\"network-tld\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku network:report --global --format json | jq -r 'has(\"tld\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku network:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "--global" + + run /bin/bash -c "dokku network:set --global tld" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(network:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku network:report --global --format json | jq -r 'has(\"global-tld\") and has(\"network-global-tld\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:report --global --format json | jq -r 'has(\"computed-tld\") and has(\"network-computed-tld\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r 'has(\"tld\") and has(\"network-tld\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r 'has(\"web-listeners\") and has(\"network-web-listeners\")'" + assert_success + assert_output "true" +} + +@test "(network:report) tld raw vs computed" { + run /bin/bash -c "dokku network:set --global tld" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-global-tld\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:set --global tld example.test" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-global-tld\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "example.test" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-global-tld\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "example.test" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-computed-tld\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "example.test" + + run /bin/bash -c "dokku network:set --global tld" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(network:report) attach-post-create raw vs computed vs global" { + docker network create create-network-x >/dev/null + run /bin/bash -c "dokku network:set --global attach-post-create" + assert_success + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-attach-post-create\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-global-attach-post-create\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-computed-attach-post-create\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:set --global attach-post-create create-network-x" + assert_success + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-global-attach-post-create\"'" + assert_success + assert_output "create-network-x" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-computed-attach-post-create\"'" + assert_success + assert_output "create-network-x" + + run /bin/bash -c "dokku network:set --global attach-post-create" + assert_success + + docker network rm create-network-x >/dev/null +} + +@test "(network:report) attach-post-deploy raw vs computed vs global" { + docker network create deploy-network-x >/dev/null + run /bin/bash -c "dokku network:set --global attach-post-deploy" + assert_success + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-attach-post-deploy\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-global-attach-post-deploy\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:set --global attach-post-deploy deploy-network-x" + assert_success + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-global-attach-post-deploy\"'" + assert_success + assert_output "deploy-network-x" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-computed-attach-post-deploy\"'" + assert_success + assert_output "deploy-network-x" + + run /bin/bash -c "dokku network:set --global attach-post-deploy" + assert_success + + docker network rm deploy-network-x >/dev/null +} + +@test "(network:report) initial-network raw vs computed vs global" { + docker network create initial-network-x >/dev/null + run /bin/bash -c "dokku network:set --global initial-network" + assert_success + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-initial-network\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-global-initial-network\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:set --global initial-network initial-network-x" + assert_success + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-global-initial-network\"'" + assert_success + assert_output "initial-network-x" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-computed-initial-network\"'" + assert_success + assert_output "initial-network-x" + + run /bin/bash -c "dokku network:set --global initial-network" + assert_success + + docker network rm initial-network-x >/dev/null +} + +@test "(network:report) bind-all-interfaces raw vs computed vs global" { + run /bin/bash -c "dokku network:set --global bind-all-interfaces" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-global-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-computed-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku network:set --global bind-all-interfaces true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-global-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-computed-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-computed-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:set --global bind-all-interfaces" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-global-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:report --global --format json | jq -r '.\"network-computed-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku network:set $TEST_APP bind-all-interfaces true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku network:set $TEST_APP bind-all-interfaces" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku network:report $TEST_APP --format json | jq -r '.\"network-computed-bind-all-interfaces\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" +} + @test "(network) network:set bind-all-interfaces" { - deploy_app - assert_nonssl_domain "${TEST_APP}.dokku.me" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_nonssl_domain "${TEST_APP}.${DOKKU_DOMAIN}" run /bin/bash -c "dokku network:set $TEST_APP bind-all-interfaces true" echo "output: $output" @@ -44,10 +341,10 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - assert_external_port $(< $CID_FILE) + assert_external_port $(<$CID_FILE) done run /bin/bash -c "dokku network:set $TEST_APP bind-all-interfaces false" @@ -59,10 +356,10 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - assert_not_external_port $(< $CID_FILE) + assert_not_external_port $(<$CID_FILE) done } @@ -77,7 +374,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "curl --silent --write-out '%{http_code}\n' `dokku url $TEST_APP` | grep 200" + run /bin/bash -c "curl --silent --write-out '%{http_code}\n' $(dokku url $TEST_APP) | grep 200" echo "output: $output" echo "status: $status" assert_success @@ -145,6 +442,63 @@ teardown() { assert_failure } +@test "(network:list) --format json exposes DokkuManaged" { + run /bin/bash -c "dokku network:create $TEST_NETWORK" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:list --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:list --format json | jq -r '.[] | select(.Name == \"bridge\") | .DokkuManaged'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku network:list --format json | jq -r '.[] | select(.Name == \"$TEST_NETWORK\") | .DokkuManaged'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku --force network:destroy $TEST_NETWORK" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(network:list) --dokku-managed filters to dokku-created networks" { + run /bin/bash -c "dokku network:create $TEST_NETWORK" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:list --dokku-managed | grep -w bridge" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku network:list --dokku-managed | grep $TEST_NETWORK" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:list --dokku-managed --format json | jq -r '.[].DokkuManaged' | sort -u" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku --force network:destroy $TEST_NETWORK" + echo "output: $output" + echo "status: $status" + assert_success +} + @test "(network) network:set attach" { run deploy_app echo "output: $output" @@ -156,11 +510,21 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku network:create create-network-2" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku network:create deploy-network" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku network:create deploy-network-2" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku network:create initial-network" echo "output: $output" echo "status: $status" @@ -180,7 +544,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_failure - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" run /bin/bash -c "dokku network:set $TEST_APP attach-post-create create-network" echo "output: $output" @@ -206,7 +570,23 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_http_success "${TEST_APP}.dokku.me" + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku network:set $TEST_APP attach-post-create create-network create-network-2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:set $TEST_APP attach-post-deploy deploy-network deploy-network-2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" run /bin/bash -c "dokku --force network:destroy create-network" echo "output: $output" @@ -224,7 +604,15 @@ teardown() { assert_success # necessary in order to remove networks in use by "dead" containers - docker container prune --force + run /bin/bash -c "docker container prune --force" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker container ls -a" + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku --force network:destroy create-network" echo "output: $output" @@ -242,8 +630,55 @@ teardown() { assert_success } +@test "(network) dont re-attach to network" { + run /bin/bash -c "dokku network:create deploy-network" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku network:set $TEST_APP attach-post-deploy deploy-network" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile-procfile + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP worker=1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=3" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker container inspect $TEST_APP.web.1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker container inspect $TEST_APP.web.2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker container inspect $TEST_APP.web.3" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker container inspect $TEST_APP.worker.1" + echo "output: $output" + echo "status: $status" + assert_success +} + @test "(network) handle single-udp app" { - run /bin/bash -c "dokku docker-options:add $TEST_APP -p 1194:1194" + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy -p 1194:1194" echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/nginx-vhosts_1.bats b/tests/unit/nginx-vhosts_1.bats index 3e1fb17f2f5..c42d1024695 100644 --- a/tests/unit/nginx-vhosts_1.bats +++ b/tests/unit/nginx-vhosts_1.bats @@ -28,11 +28,116 @@ teardown() { assert_output "$help_output" } -@test "(nginx-vhosts) nginx:build-config (domains:disable/enable)" { - deploy_app - dokku domains:disable $TEST_APP +@test "(nginx:report) info-flag works before deploy" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:report $TEST_APP --nginx-bind-address-ipv4" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:set $TEST_APP bind-address-ipv4 127.0.0.1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:report $TEST_APP --nginx-bind-address-ipv4" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "127.0.0.1" + + run /bin/bash -c "dokku nginx:report $TEST_APP --nginx-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(nginx:report) --format json" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success - HOSTNAME=$(< "$DOKKU_ROOT/VHOST") + run /bin/bash -c "dokku nginx:set $TEST_APP bind-address-ipv4 127.0.0.1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json | jq -r '.\"bind-address-ipv4\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "127.0.0.1" + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:report --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json --nginx-bind-address-ipv4" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "--format flag cannot be specified when specifying an info flag" +} + +@test "(nginx:report) --global --format json" { + run /bin/bash -c "dokku nginx:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:report --global --format json | jq -r 'keys[]' | grep -v 'global-' | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "0" + + run /bin/bash -c "dokku nginx:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global nginx information" + + run /bin/bash -c "dokku nginx:set --global client-max-body-size 5m" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:report --global --nginx-global-client-max-body-size" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5m" + + run /bin/bash -c "dokku nginx:set --global client-max-body-size" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(nginx-vhosts) proxy:build-config (domains:disable/enable)" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:disable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + HOSTNAME=$(<"$DOKKU_ROOT/VHOST") check_urls http://${HOSTNAME}:[0-9]+ URLS=$(dokku --quiet urls "$TEST_APP") @@ -40,29 +145,42 @@ teardown() { assert_http_success $URL done - dokku domains:enable $TEST_APP - check_urls http://${TEST_APP}.dokku.me - assert_http_success http://${TEST_APP}.dokku.me + run /bin/bash -c "dokku domains:enable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } -@test "(nginx-vhosts) nginx:build-config (domains:add pre deploy)" { - create_app - run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.dokku.me" +@test "(nginx-vhosts) proxy:build-config (domains:add pre deploy)" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success sleep 5 # wait for nginx to reload - check_urls http://www.test.app.dokku.me - assert_http_success http://www.test.app.dokku.me + check_urls "http://www.test.app.${DOKKU_DOMAIN}" + assert_http_success "http://www.test.app.${DOKKU_DOMAIN}" } -@test "(nginx-vhosts) nginx:build-config (with global VHOST)" { - echo "dokku.me" > "$DOKKU_ROOT/VHOST" - deploy_app +@test "(nginx-vhosts) proxy:build-config (with global VHOST)" { + echo "${DOKKU_DOMAIN}" >"$DOKKU_ROOT/VHOST" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success - check_urls http://${TEST_APP}.dokku.me - assert_http_success http://${TEST_APP}.dokku.me + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}" + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" } diff --git a/tests/unit/nginx-vhosts_10.bats b/tests/unit/nginx-vhosts_10.bats index 7377d6e3299..75878a3bc16 100644 --- a/tests/unit/nginx-vhosts_10.bats +++ b/tests/unit/nginx-vhosts_10.bats @@ -9,7 +9,7 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @@ -20,7 +20,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -35,7 +35,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -52,7 +52,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -67,7 +67,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -84,7 +84,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -99,7 +99,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -116,7 +116,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -131,7 +131,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -146,7 +146,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -161,7 +161,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -171,4 +171,3 @@ teardown() { echo "status: $status" assert_output_contains "X-Forwarded-Ssl" 0 } - diff --git a/tests/unit/nginx-vhosts_11.bats b/tests/unit/nginx-vhosts_11.bats index dfba17844d1..0bc9d11e767 100644 --- a/tests/unit/nginx-vhosts_11.bats +++ b/tests/unit/nginx-vhosts_11.bats @@ -9,20 +9,23 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) nginx:set proxy-buffer-size" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-buffer-size 2k" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -37,7 +40,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -49,14 +52,17 @@ teardown() { } @test "(nginx-vhosts) nginx:set proxy-buffering" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-buffering off" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -71,7 +77,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -83,14 +89,17 @@ teardown() { } @test "(nginx-vhosts) nginx:set proxy-buffers" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-buffers \"64 4k\"" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -105,7 +114,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -117,14 +126,17 @@ teardown() { } @test "(nginx-vhosts) nginx:set proxy-busy-buffers-size" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-busy-buffers-size 10k" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -139,7 +151,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -149,3 +161,66 @@ teardown() { echo "status: $status" assert_output_contains "proxy_busy_buffers_size 10k;" 0 } + +@test "(nginx-vhosts) nginx:set nginx.conf.sigil" { + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM + export CUSTOM_TMP="$TMP" + + run deploy_app python + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 0 + + pushd $TMP + custom_nginx_template "$TEST_APP" "$CUSTOM_TMP" + + run /bin/bash -c "git add nginx.conf.sigil" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git commit -m 'Add custom nginx.conf.sigil'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git push target master:master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 2 + + mv "$CUSTOM_TMP/nginx.conf.sigil" "$CUSTOM_TMP/nginx.conf.sigil-2" + + run /bin/bash -c "git add nginx.conf.sigil nginx.conf.sigil-2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git commit -m 'Moving nginx.conf.sigil'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git push target master:master" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 0 + + run /bin/bash -c "dokku nginx:set $TEST_APP nginx-conf-sigil-path nginx.conf.sigil-2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 2 + + popd + rm -rf "$TMP" +} diff --git a/tests/unit/nginx-vhosts_12.bats b/tests/unit/nginx-vhosts_12.bats new file mode 100644 index 00000000000..95b6af06929 --- /dev/null +++ b/tests/unit/nginx-vhosts_12.bats @@ -0,0 +1,131 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + create_app +} + +teardown() { + destroy_app + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + global_teardown +} + +@test "(nginx-vhosts) git:from-image nginx.conf.sigil" { + local CUSTOM_TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$CUSTOM_TMP"' INT TERM + rmdir "$CUSTOM_TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/python" "$CUSTOM_TMP" + + pushd $CUSTOM_TMP + custom_nginx_template "$TEST_APP" "$CUSTOM_TMP" + + run /bin/bash -c "dokku nginx:set --global nginx-conf-sigil-path" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git config --global init.defaultBranch master" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git init" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git add nginx.conf.sigil" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git commit -m 'Add custom nginx.conf.sigil'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image build -t dokku-test/$TEST_APP:latest -f $CUSTOM_TMP/alt.Dockerfile $CUSTOM_TMP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP dokku-test/$TEST_APP:latest" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 2 + + run /bin/bash -c "dokku nginx:set --global nginx-conf-sigil-path dokku/nginx.conf.sigil" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 0 + + run /bin/bash -c "mkdir -p $CUSTOM_TMP/dokku" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "mv $CUSTOM_TMP/nginx.conf.sigil $CUSTOM_TMP/dokku/nginx.conf.sigil" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git add ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "git commit -m 'Move the nginx.conf.sigil'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image build -t dokku-test/$TEST_APP:v2 -f $CUSTOM_TMP/alt.Dockerfile $CUSTOM_TMP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP dokku-test/$TEST_APP:v2" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 2 + + run /bin/bash -c "dokku nginx:set --global nginx-conf-sigil-path" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 0 + + run /bin/bash -c "dokku nginx:set $TEST_APP nginx-conf-sigil-path dokku/nginx.conf.sigil" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Overriding default nginx.conf with detected nginx.conf.sigil" 2 + + docker image rm dokku-test/$TEST_APP:latest dokku-test/$TEST_APP:v2 || true +} diff --git a/tests/unit/nginx-vhosts_13.bats b/tests/unit/nginx-vhosts_13.bats new file mode 100644 index 00000000000..37845e6fcef --- /dev/null +++ b/tests/unit/nginx-vhosts_13.bats @@ -0,0 +1,99 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" +} + +teardown() { + destroy_app + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + global_teardown +} + +@test "(nginx-vhosts) proxy:build-config generates 502 config for undeployed app" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "return 502" -1 +} + +@test "(nginx-vhosts) proxy:build-config undeployed app returns 502" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + sleep 2 + + assert_http_localhost_response "http" "${TEST_APP}.${DOKKU_DOMAIN}" "80" "/" "" "502" +} + +@test "(nginx-vhosts) proxy:build-config 502 config replaced after deploy" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "return 502" 0 + assert_output_contains "proxy_pass" -1 + + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}" +} + +@test "(nginx-vhosts) proxy:build-config domains:add updates 502 config" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP custom.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "custom.${DOKKU_DOMAIN}" -1 + assert_output_contains "return 502" -1 +} + +@test "(nginx-vhosts) proxy:build-config no nginx.conf when proxy disabled" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:disable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" + echo "output: $output" + echo "status: $status" + assert_failure +} diff --git a/tests/unit/nginx-vhosts_14.bats b/tests/unit/nginx-vhosts_14.bats new file mode 100644 index 00000000000..4825bdaf697 --- /dev/null +++ b/tests/unit/nginx-vhosts_14.bats @@ -0,0 +1,151 @@ +#!/usr/bin/env bats + +# Tests for the nginx:reload command and the catch-all default site shipped +# at /etc/nginx/conf.d/00-default-vhost.conf. Postinst-driven behavior +# (debconf flag, fresh-install detection, sites-enabled/default rename) is +# not exercised here because the bats harness does not reinstall the apt +# package; the rename logic is exercised manually by simulating the +# conflicting upstream vhost. + +load test_helper + +NGINX_DEFAULT_VHOST_PATH="/etc/nginx/conf.d/00-default-vhost.conf" +NGINX_DEFAULT_VHOST_SOURCE_MODERN="/var/lib/dokku/core-plugins/available/nginx-vhosts/templates/default-site.conf" +NGINX_DEFAULT_VHOST_SOURCE_LEGACY="/var/lib/dokku/core-plugins/available/nginx-vhosts/templates/default-site-legacy.conf" + +fn-nginx-supports-ssl-reject-handshake() { + local nginx_version major minor patch + nginx_version="$(nginx -v 2>&1 | cut -d'/' -f 2 | awk '{print $1}')" + major="$(echo "$nginx_version" | awk -F. '{print $1}')" + minor="$(echo "$nginx_version" | awk -F. '{print $2}')" + patch="$(echo "$nginx_version" | awk -F. '{print $3}')" + if [ "${major:-0}" -ge 2 ]; then + return 0 + fi + if [ "${major:-0}" -eq 1 ] && [ "${minor:-0}" -gt 19 ]; then + return 0 + fi + if [ "${major:-0}" -eq 1 ] && [ "${minor:-0}" -eq 19 ] && [ "${patch:-0}" -ge 4 ]; then + return 0 + fi + return 1 +} + +fn-default-site-source() { + if fn-nginx-supports-ssl-reject-handshake; then + echo "$NGINX_DEFAULT_VHOST_SOURCE_MODERN" + else + echo "$NGINX_DEFAULT_VHOST_SOURCE_LEGACY" + fi +} + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + [[ -f "$NGINX_DEFAULT_VHOST_PATH" ]] && cp -fp "$NGINX_DEFAULT_VHOST_PATH" "${NGINX_DEFAULT_VHOST_PATH}.bats-bak" + rm -f "$NGINX_DEFAULT_VHOST_PATH" +} + +teardown() { + rm -f "$NGINX_DEFAULT_VHOST_PATH" + if [[ -f "${NGINX_DEFAULT_VHOST_PATH}.bats-bak" ]]; then + mv "${NGINX_DEFAULT_VHOST_PATH}.bats-bak" "$NGINX_DEFAULT_VHOST_PATH" + fi + rm -f /etc/nginx/sites-enabled/default.bats-stub + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + if sudo nginx -t &>/dev/null; then + sudo systemctl reload nginx || true + fi + global_teardown +} + +@test "(nginx-vhosts:reload) reloads with valid config" { + run /bin/bash -c "dokku nginx:reload" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(nginx-vhosts:reload) fails with invalid config without restarting" { + local invalid_conf="/etc/nginx/conf.d/dokku-bats-invalid.conf" + echo "this is not valid nginx config" | sudo tee "$invalid_conf" >/dev/null + + run /bin/bash -c "dokku nginx:reload" + echo "output: $output" + echo "status: $status" + assert_failure + + sudo rm -f "$invalid_conf" +} + +@test "(nginx-vhosts) [default-site] modern template installs and validates on nginx >= 1.19.4" { + fn-nginx-supports-ssl-reject-handshake || skip "nginx < 1.19.4 does not support ssl_reject_handshake" + [[ -f "$NGINX_DEFAULT_VHOST_SOURCE_MODERN" ]] || skip "default-site template not installed; run 'sudo make copyfiles'" + + sudo install -m 0644 -o root -g root "$NGINX_DEFAULT_VHOST_SOURCE_MODERN" "$NGINX_DEFAULT_VHOST_PATH" + + run /bin/bash -c "sudo grep -F 'ssl_reject_handshake on' '$NGINX_DEFAULT_VHOST_PATH'" + assert_success + run /bin/bash -c "sudo grep -F 'return 444' '$NGINX_DEFAULT_VHOST_PATH'" + assert_success + run /bin/bash -c "sudo grep -F 'default_server' '$NGINX_DEFAULT_VHOST_PATH'" + assert_success + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(nginx-vhosts) [default-site] legacy template installs and validates on nginx < 1.19.4" { + if fn-nginx-supports-ssl-reject-handshake; then + skip "nginx >= 1.19.4 uses the modern default-site template" + fi + [[ -f "$NGINX_DEFAULT_VHOST_SOURCE_LEGACY" ]] || skip "default-site-legacy template not installed; run 'sudo make copyfiles'" + + sudo install -m 0644 -o root -g root "$NGINX_DEFAULT_VHOST_SOURCE_LEGACY" "$NGINX_DEFAULT_VHOST_PATH" + + run /bin/bash -c "sudo grep -F 'return 444' '$NGINX_DEFAULT_VHOST_PATH'" + assert_success + run /bin/bash -c "sudo grep -F 'default_server' '$NGINX_DEFAULT_VHOST_PATH'" + assert_success + run /bin/bash -c "sudo grep -F 'ssl_reject_handshake' '$NGINX_DEFAULT_VHOST_PATH'" + assert_failure + run /bin/bash -c "sudo grep -E 'listen[[:space:]]+(\\[::\\]:)?443' '$NGINX_DEFAULT_VHOST_PATH'" + assert_failure + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(nginx-vhosts) [default-site] coexists with stock sites-enabled/default" { + local default_vhost_source + default_vhost_source="$(fn-default-site-source)" + [[ -f "$default_vhost_source" ]] || skip "default-site template not installed; run 'sudo make copyfiles'" + + sudo mkdir -p /etc/nginx/sites-enabled + sudo tee /etc/nginx/sites-enabled/default.bats-stub >/dev/null <<'STOCK' +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + return 200 "stock default"; +} +STOCK + + sudo install -m 0644 -o root -g root "$default_vhost_source" "$NGINX_DEFAULT_VHOST_PATH" + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_failure + + sudo rm -f /etc/nginx/sites-enabled/default.bats-stub + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/tests/unit/nginx-vhosts_15.bats b/tests/unit/nginx-vhosts_15.bats new file mode 100644 index 00000000000..3de423ca1c4 --- /dev/null +++ b/tests/unit/nginx-vhosts_15.bats @@ -0,0 +1,172 @@ +#!/usr/bin/env bats + +load test_helper + +NGINX_VALIDATE_PLUGIN_NAME="test-nginx-validate" +NGINX_VALIDATE_ZONE_CONF="/etc/nginx/conf.d/00-dokku-test-limit-req.conf" + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + create_app +} + +teardown() { + rm -rf "${PLUGIN_ENABLED_PATH:?}/$NGINX_VALIDATE_PLUGIN_NAME" "${PLUGIN_AVAILABLE_PATH:?}/$NGINX_VALIDATE_PLUGIN_NAME" + destroy_app + rm -f "$NGINX_VALIDATE_ZONE_CONF" + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + global_teardown +} + +@test "(nginx-vhosts) pre-validate fails fast on broken nginx.conf.sigil" { + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP bad_custom_nginx_template + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Pre-validating custom nginx.conf.sigil" + assert_output_contains "Custom nginx.conf.sigil failed nginx -t validation" + assert_output_not_contains "Building $TEST_APP" + assert_output_not_contains "Releasing $TEST_APP" +} + +@test "(nginx-vhosts) pre-validate succeeds on first deploy with valid nginx.conf.sigil" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Pre-validating custom nginx.conf.sigil" +} + +@test "(nginx-vhosts) pre-validate is skipped when disable-custom-config=true" { + run /bin/bash -c "dokku nginx:set $TEST_APP disable-custom-config true" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP bad_custom_nginx_template + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "Pre-validating custom nginx.conf.sigil" +} + +@test "(nginx-vhosts) pre-validate is skipped when proxy is not nginx" { + run /bin/bash -c "dokku proxy:set $TEST_APP caddy" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP bad_custom_nginx_template + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "Pre-validating custom nginx.conf.sigil" +} + +@test "(nginx-vhosts) pre-validate fails when nginx.conf.sigil needs a directive absent from the wrapper" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template_with_limit_req + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Pre-validating custom nginx.conf.sigil" + assert_output_contains "Custom nginx.conf.sigil failed nginx -t validation" +} + +@test "(nginx-vhosts) pre-validate passes when nginx-app-template-source overrides validate-config" { + echo 'limit_req_zone $binary_remote_addr zone=dokkuprevalidate:10m rate=100r/s;' >"$NGINX_VALIDATE_ZONE_CONF" + setup_nginx_validate_plugin + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template_with_limit_req + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Pre-validating custom nginx.conf.sigil" +} + +setup_nginx_validate_plugin() { + declare desc="installs a plugin that overrides the validate-config nginx template" + local PLUGIN_DIR="$PLUGIN_AVAILABLE_PATH/$NGINX_VALIDATE_PLUGIN_NAME" + mkdir -p "$PLUGIN_DIR" + + cat <"$PLUGIN_DIR/plugin.toml" +[plugin] +description = "test plugin overriding the validate-config nginx template" +version = "0.1.0" +[plugin.config] +EOF + + cat <<'EOF' >"$PLUGIN_DIR/nginx-app-template-source" +#!/usr/bin/env bash + +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +TEMPLATE_TYPE="$2" +if [[ "$TEMPLATE_TYPE" == "validate-config" ]]; then + echo "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/validate.conf.sigil" +fi +EOF + chmod +x "$PLUGIN_DIR/nginx-app-template-source" + + cat <<'EOF' >"$PLUGIN_DIR/validate.conf.sigil" +events { worker_connections 768; } +http { + access_log off; + error_log /dev/null; + limit_req_zone $binary_remote_addr zone=dokkuprevalidate:10m rate=100r/s; + include {{ $.NGINX_CONF }}; +} +EOF + + dokku plugin:enable "$NGINX_VALIDATE_PLUGIN_NAME" +} + +custom_nginx_template_with_limit_req() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mkdir -p "$APP_REPO_DIR" + + echo "injecting custom_nginx_template_with_limit_req -> $APP_REPO_DIR/nginx.conf.sigil" + cat <"$APP_REPO_DIR/nginx.conf.sigil" +{{ range \$port_map := .PROXY_PORT_MAP | split " " }} +{{ \$port_map_list := \$port_map | split ":" }} +{{ \$scheme := index \$port_map_list 0 }} +{{ \$listen_port := index \$port_map_list 1 }} +{{ \$upstream_port := index \$port_map_list 2 }} + +server { + listen [::]:{{ \$listen_port }}; + listen {{ \$listen_port }}; + server_name {{ $.NOSSL_SERVER_NAME }} customtemplate.${DOKKU_DOMAIN}; + + location / { + limit_req zone=dokkuprevalidate burst=100 nodelay; + proxy_pass http://{{ $.APP }}-{{ \$upstream_port }}; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host \$http_host; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_set_header X-Forwarded-For \$remote_addr; + proxy_set_header X-Forwarded-Port \$server_port; + proxy_set_header X-Request-Start \$msec; + } + include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; +} +{{ end }} + +{{ if $.DOKKU_APP_WEB_LISTENERS }} +{{ range \$upstream_port := $.PROXY_UPSTREAM_PORTS | split " " }} +upstream {{ $.APP }}-{{ \$upstream_port }} { +{{ range \$listeners := $.DOKKU_APP_WEB_LISTENERS | split " " }} +{{ \$listener_list := \$listeners | split ":" }} +{{ \$listener_ip := index \$listener_list 0 }} + server {{ \$listener_ip }}:{{ \$upstream_port }};{{ end }} +} +{{ end }}{{ end }} + +EOF + cat "$APP_REPO_DIR/nginx.conf.sigil" +} diff --git a/tests/unit/nginx-vhosts_16.bats b/tests/unit/nginx-vhosts_16.bats new file mode 100644 index 00000000000..33c907adf9f --- /dev/null +++ b/tests/unit/nginx-vhosts_16.bats @@ -0,0 +1,71 @@ +#!/usr/bin/env bats + +load test_helper + +setup_local_tls() { + TLS=$BATS_TMPDIR/tls + mkdir -p $TLS + tar xf $BATS_TEST_DIRNAME/server_ssl.tar -C $TLS + tar xf $BATS_TEST_DIRNAME/domain_ssl.tar -C $TLS + sudo chown -R dokku:dokku $TLS +} + +teardown_local_tls() { + TLS=$BATS_TMPDIR/tls + rm -R $TLS +} + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + create_app +} + +teardown() { + destroy_app + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + global_teardown +} + +@test "(nginx-vhosts) refactored template generates nginx -t parseable http config" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(nginx-vhosts) refactored template generates nginx -t parseable https config" { + setup_local_tls + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sudo nginx -t" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "ssl_certificate" -1 + assert_output_contains "return 301 https" -1 + teardown_local_tls +} diff --git a/tests/unit/nginx-vhosts_2.bats b/tests/unit/nginx-vhosts_2.bats index f8f26ea5c67..441ef1ed25f 100644 --- a/tests/unit/nginx-vhosts_2.bats +++ b/tests/unit/nginx-vhosts_2.bats @@ -9,33 +9,69 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } -@test "(nginx-vhosts) nginx:build-config (with SSL and unrelated domain)" { +@test "(nginx-vhosts) proxy:build-config (with SSL and unrelated domain)" { setup_test_tls - add_domain "node-js-app.dokku.me" - add_domain "test.dokku.me" - deploy_app - dokku nginx:show-config $TEST_APP - assert_ssl_domain "node-js-app.dokku.me" - assert_http_redirect "http://test.dokku.me" "https://test.dokku.me:443/" + run /bin/bash -c "dokku domains:add $TEST_APP node-js-app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "node-js-app.${DOKKU_DOMAIN}" + assert_http_redirect "http://test.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}:443/" } -@test "(nginx-vhosts) nginx:build-config (wildcard SSL)" { +@test "(nginx-vhosts) proxy:build-config (wildcard SSL)" { setup_test_tls wildcard - add_domain "wildcard1.dokku.me" - add_domain "wildcard2.dokku.me" - deploy_app - dokku nginx:show-config $TEST_APP - assert_ssl_domain "wildcard1.dokku.me" - assert_ssl_domain "wildcard2.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP wildcard1.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP wildcard2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "wildcard1.${DOKKU_DOMAIN}" + assert_ssl_domain "wildcard2.${DOKKU_DOMAIN}" } -@test "(nginx-vhosts) nginx:build-config (wildcard SSL and unrelated domain) 1" { - destroy_app +@test "(nginx-vhosts) proxy:build-config (wildcard SSL and unrelated domain) 1" { + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success + TEST_APP="${TEST_APP}.example.com" run /bin/bash -c "dokku apps:create $TEST_APP" echo "output: $output" @@ -43,19 +79,46 @@ teardown() { assert_success setup_test_tls wildcard - deploy_app nodejs-express dokku@dokku.me:$TEST_APP - run /bin/bash -c "dokku nginx:show-config $TEST_APP | grep -e '*.dokku.me' | wc -l" - dokku nginx:show-config $TEST_APP + + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP | grep -e '*.${DOKKU_DOMAIN}' | wc -l" + echo "output: $output" + echo "status: $status" + assert_success assert_output "0" } -@test "(nginx-vhosts) nginx:build-config (with SSL and Multiple SANs)" { +@test "(nginx-vhosts) proxy:build-config (with SSL and Multiple SANs)" { setup_test_tls sans - add_domain "test.dokku.me" - add_domain "www.test.dokku.me" - add_domain "www.test.app.dokku.me" - deploy_app - assert_ssl_domain "test.dokku.me" - assert_ssl_domain "www.test.dokku.me" - assert_ssl_domain "www.test.app.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_ssl_domain "test.${DOKKU_DOMAIN}" + assert_ssl_domain "www.test.${DOKKU_DOMAIN}" + assert_ssl_domain "www.test.app.${DOKKU_DOMAIN}" } diff --git a/tests/unit/nginx-vhosts_3.bats b/tests/unit/nginx-vhosts_3.bats index babc690c5fd..909d656c6bb 100644 --- a/tests/unit/nginx-vhosts_3.bats +++ b/tests/unit/nginx-vhosts_3.bats @@ -11,29 +11,67 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) grpc endpoint" { - deploy_app gogrpc - dokku proxy:ports-add "$TEST_APP" "grpc:80:50051" - run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.dokku.me:80 -name grpc" - assert_output "Greeting: Hello grpc" + run deploy_app gogrpc + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP grpc:80:50051" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -addr ${TEST_APP}.${DOKKU_DOMAIN}:80 -name grpc" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Greeting: Hello grpc" } @test "(nginx-vhosts) grpc endpoint on a port other than 80" { - deploy_app gogrpc - dokku proxy:ports-add "$TEST_APP" "grpc:8080:50051" - run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.dokku.me:8080 -name grpc8080" - assert_output "Greeting: Hello grpc8080" + run deploy_app gogrpc + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP grpc:8080:50051" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -addr ${TEST_APP}.${DOKKU_DOMAIN}:8080 -name grpc8080" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Greeting: Hello grpc8080" } @test "(nginx-vhosts) grpcs endpoint" { setup_test_tls - deploy_app gogrpc - dokku proxy:ports-add "$TEST_APP" "grpcs:443:50051" - run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -address ${TEST_APP}.dokku.me:443 -name grpcs -tls" - assert_output "Greeting: Hello grpcs" + run deploy_app gogrpc + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP grpcs:443:50051" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker run --rm ${TEST_APP}-docker-image /go/bin/greeter_client -addr ${TEST_APP}.${DOKKU_DOMAIN}:443 -name grpcs -tls" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Greeting: Hello grpcs" } diff --git a/tests/unit/nginx-vhosts_4.bats b/tests/unit/nginx-vhosts_4.bats index f46d7b68f0d..d9a28117921 100644 --- a/tests/unit/nginx-vhosts_4.bats +++ b/tests/unit/nginx-vhosts_4.bats @@ -14,15 +14,23 @@ teardown() { global_teardown } -@test "(nginx-vhosts) nginx:build-config (without global VHOST)" { +@test "(nginx-vhosts) proxy:build-config (without global VHOST)" { rm "$DOKKU_ROOT/VHOST" - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku --quiet urls $TEST_APP" echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku ports:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + HOSTNAME="$(hostname -f)" check_urls http://${HOSTNAME}:[0-9]+ @@ -32,12 +40,15 @@ teardown() { done } -@test "(nginx-vhosts) nginx:build-config (without global VHOST and IPv4 address set as HOSTNAME)" { +@test "(nginx-vhosts) proxy:build-config (without global VHOST and IPv4 address set as HOSTNAME)" { rm "$DOKKU_ROOT/VHOST" - echo "127.0.0.1" > "$DOKKU_ROOT/VHOST" - deploy_app + echo "127.0.0.1" >"$DOKKU_ROOT/VHOST" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success - HOSTNAME=$(< "$DOKKU_ROOT/VHOST") + HOSTNAME=$(<"$DOKKU_ROOT/VHOST") check_urls http://${HOSTNAME}:[0-9]+ URLS=$(dokku --quiet urls "$TEST_APP") @@ -46,27 +57,49 @@ teardown() { done } -@test "(nginx-vhosts) nginx:build-config (without global VHOST and IPv6 address set as HOSTNAME)" { +@test "(nginx-vhosts) proxy:build-config (without global VHOST and IPv6 address set as HOSTNAME)" { rm "$DOKKU_ROOT/VHOST" - echo "fda5:c7db:a520:bb6d::aabb:ccdd:eeff" > "$DOKKU_ROOT/VHOST" - deploy_app + echo "fda5:c7db:a520:bb6d::aabb:ccdd:eeff" >"$DOKKU_ROOT/VHOST" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success - HOSTNAME=$(< "$DOKKU_ROOT/VHOST") + HOSTNAME=$(<"$DOKKU_ROOT/VHOST") check_urls http://${HOSTNAME}:[0-9]+ } -@test "(nginx-vhosts) nginx:build-config (without global VHOST and domains:add pre deploy)" { +@test "(nginx-vhosts) proxy:build-config (without global VHOST and domains:add pre deploy)" { rm "$DOKKU_ROOT/VHOST" - create_app - add_domain "www.test.app.dokku.me" - deploy_app - assert_nonssl_domain "www.test.app.dokku.me" + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_nonssl_domain "www.test.app.${DOKKU_DOMAIN}" } -@test "(nginx-vhosts) nginx:build-config (without global VHOST and domains:add post deploy)" { +@test "(nginx-vhosts) proxy:build-config (without global VHOST and domains:add post deploy)" { rm "$DOKKU_ROOT/VHOST" - deploy_app - add_domain "www.test.app.dokku.me" - check_urls http://www.test.app.dokku.me - assert_http_success http://www.test.app.dokku.me + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + check_urls "http://www.test.app.${DOKKU_DOMAIN}" + assert_http_success "http://www.test.app.${DOKKU_DOMAIN}" } diff --git a/tests/unit/nginx-vhosts_5.bats b/tests/unit/nginx-vhosts_5.bats index a6c38ed0055..9af7c2ea024 100644 --- a/tests/unit/nginx-vhosts_5.bats +++ b/tests/unit/nginx-vhosts_5.bats @@ -15,46 +15,67 @@ teardown() { global_teardown } -@test "(nginx-vhosts) nginx:build-config (sslip.io style hostnames)" { - echo "127.0.0.1.sslip.io.dokku.me" > "$DOKKU_ROOT/VHOST" - deploy_app +@test "(nginx-vhosts) proxy:build-config (sslip.io style hostnames)" { + echo "127.0.0.1.sslip.io.${DOKKU_DOMAIN}" >"$DOKKU_ROOT/VHOST" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success - check_urls http://${TEST_APP}.127.0.0.1.sslip.io.dokku.me - assert_http_success http://${TEST_APP}.127.0.0.1.sslip.io.dokku.me + check_urls "http://${TEST_APP}.127.0.0.1.sslip.io.${DOKKU_DOMAIN}" + assert_http_success "http://${TEST_APP}.127.0.0.1.sslip.io.${DOKKU_DOMAIN}" } -@test "(nginx-vhosts) nginx:build-config (dockerfile expose)" { - deploy_app dockerfile +@test "(nginx-vhosts) proxy:build-config (dockerfile expose)" { + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success - add_domain "www.test.app.dokku.me" - check_urls http://${TEST_APP}.dokku.me:3000 - check_urls http://${TEST_APP}.dokku.me:3003 - check_urls http://www.test.app.dokku.me:3000 - check_urls http://www.test.app.dokku.me:3003 - assert_http_success http://${TEST_APP}.dokku.me:3000 - assert_http_success http://${TEST_APP}.dokku.me:3003 - assert_http_success http://www.test.app.dokku.me:3000 - assert_http_success http://www.test.app.dokku.me:3003 + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}:3000" + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}:3003" + check_urls "http://www.test.app.${DOKKU_DOMAIN}:3000" + check_urls "http://www.test.app.${DOKKU_DOMAIN}:3003" + assert_http_localhost_response "http" "${TEST_APP}.${DOKKU_DOMAIN}" "3000" + assert_http_localhost_response "http" "${TEST_APP}.${DOKKU_DOMAIN}" "3003" + assert_http_localhost_response "http" "www.test.app.${DOKKU_DOMAIN}" "3000" + assert_http_localhost_response "http" "www.test.app.${DOKKU_DOMAIN}" "3003" } -@test "(nginx-vhosts) nginx:build-config (multiple networks)" { - deploy_app +@test "(nginx-vhosts) proxy:build-config (multiple networks)" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success create_attach_network - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success } -@test "(nginx-vhosts) nginx:build-config (global DOKKU_PROXY_PORT)" { +@test "(nginx-vhosts) proxy:build-config (global proxy-port)" { local GLOBAL_PORT=30999 - run /bin/bash -c "dokku config:set --global DOKKU_PROXY_PORT=${GLOBAL_PORT}" + run /bin/bash -c "dokku proxy:set --global proxy-port ${GLOBAL_PORT}" + echo "output: $output" + echo "status: $status" + assert_success - deploy_app - check_urls http://${TEST_APP}.dokku.me:${GLOBAL_PORT} - assert_http_success http://${TEST_APP}.dokku.me:${GLOBAL_PORT} + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + check_urls "http://${TEST_APP}.${DOKKU_DOMAIN}:${GLOBAL_PORT}" + assert_http_success "http://${TEST_APP}.${DOKKU_DOMAIN}:${GLOBAL_PORT}" - run /bin/bash -c "dokku config:unset --global DOKKU_PROXY_PORT" + run /bin/bash -c "dokku proxy:set --global proxy-port" + echo "output: $output" + echo "status: $status" + assert_success } diff --git a/tests/unit/nginx-vhosts_6.bats b/tests/unit/nginx-vhosts_6.bats index 38ad54a0b7f..15ec9272580 100644 --- a/tests/unit/nginx-vhosts_6.bats +++ b/tests/unit/nginx-vhosts_6.bats @@ -9,13 +9,16 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) nginx (no server tokens)" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "curl -s -D - $(dokku url $TEST_APP) -o /dev/null | grep -E '^Server' | grep -E '[0-9]+'" echo "output: $output" echo "status: $status" @@ -30,7 +33,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "Enabling HSTS" + assert_output_contains "Enabling HSTS" 2 run /bin/bash -c "test -f $HSTS_CONF" echo "output: $output" @@ -53,7 +56,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -66,7 +69,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -79,7 +82,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -93,7 +96,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -113,7 +116,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - assert_output_contains "Enabling HSTS" + assert_output_contains "Enabling HSTS" 2 # disable hsts globally run /bin/bash -c "dokku nginx:set --global hsts false" @@ -171,7 +174,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -192,7 +195,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -205,7 +208,10 @@ teardown() { } @test "(nginx-vhosts) nginx:validate-config" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:validate-config" echo "output: $output" echo "status: $status" @@ -216,7 +222,7 @@ teardown() { echo "status: $status" assert_success - echo "invalid config" > "/home/dokku/${TEST_APP}/nginx.conf" + echo "invalid config" >"/home/dokku/${TEST_APP}/nginx.conf" run /bin/bash -c "dokku nginx:validate-config" echo "output: $output" @@ -238,7 +244,7 @@ teardown() { echo "status: $status" assert_success - echo "invalid config" > "/home/dokku/${TEST_APP}/nginx.conf" + echo "invalid config" >"/home/dokku/${TEST_APP}/nginx.conf" run /bin/bash -c "dokku nginx:validate-config $TEST_APP --clean" echo "output: $output" diff --git a/tests/unit/nginx-vhosts_7.bats b/tests/unit/nginx-vhosts_7.bats index 52417e403a9..591266eb15b 100644 --- a/tests/unit/nginx-vhosts_7.bats +++ b/tests/unit/nginx-vhosts_7.bats @@ -9,26 +9,41 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } -@test "(nginx-vhosts) nginx:build-config (wildcard SSL and custom nginx template)" { +@test "(nginx-vhosts) proxy:build-config (wildcard SSL and custom nginx template)" { setup_test_tls wildcard - add_domain "wildcard1.dokku.me" - add_domain "wildcard2.dokku.me" - deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_ssl_nginx_template + run /bin/bash -c "dokku domains:add $TEST_APP wildcard1.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP wildcard2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success - assert_ssl_domain "wildcard1.dokku.me" - assert_ssl_domain "wildcard2.dokku.me" + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP custom_ssl_nginx_template + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "wildcard1.${DOKKU_DOMAIN}" + assert_ssl_domain "wildcard2.${DOKKU_DOMAIN}" assert_http_redirect "http://${CUSTOM_TEMPLATE_SSL_DOMAIN}" "https://${CUSTOM_TEMPLATE_SSL_DOMAIN}:443/" assert_http_success "https://${CUSTOM_TEMPLATE_SSL_DOMAIN}" } -@test "(nginx-vhosts) nginx:build-config (custom nginx template - no ssl)" { - add_domain "www.test.app.dokku.me" - run deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_nginx_template +@test "(nginx-vhosts) proxy:build-config (custom nginx template - no ssl)" { + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template echo "output: $output" echo "status: $status" assert_success @@ -38,8 +53,8 @@ teardown() { echo "status: $status" assert_success - assert_nonssl_domain "www.test.app.dokku.me" - assert_http_success "customtemplate.dokku.me" + assert_nonssl_domain "www.test.app.${DOKKU_DOMAIN}" + assert_http_localhost_response "http" "customtemplate.${DOKKU_DOMAIN}" run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" @@ -47,14 +62,18 @@ teardown() { assert_output_contains "${TEST_APP}-worker-5000" } -@test "(nginx-vhosts) nginx:build-config (disable custom nginx template - no ssl)" { +@test "(nginx-vhosts) proxy:build-config (disable custom nginx template - no ssl)" { run /bin/bash -c "dokku nginx:set $TEST_APP disable-custom-config true" echo "output: $output" echo "status: $status" assert_success - add_domain "www.test.app.dokku.me" - run deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_nginx_template + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template echo "output: $output" echo "status: $status" assert_success @@ -64,7 +83,7 @@ teardown() { echo "status: $status" assert_success - assert_nonssl_domain "www.test.app.dokku.me" + assert_nonssl_domain "www.test.app.${DOKKU_DOMAIN}" run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" @@ -72,9 +91,72 @@ teardown() { assert_output_contains "${TEST_APP}-worker-5000" 0 } -@test "(nginx-vhosts) nginx:build-config (failed validate_nginx)" { - run deploy_app nodejs-express dokku@dokku.me:$TEST_APP bad_custom_nginx_template +@test "(nginx-vhosts) proxy:build-config (failed validate_nginx)" { + run deploy_app nodejs-express dokku@$DOKKU_DOMAIN:$TEST_APP bad_custom_nginx_template echo "output: $output" echo "status: $status" assert_failure + assert_output_contains "Pre-validating custom nginx.conf.sigil" + assert_output_contains "Custom nginx.conf.sigil failed nginx -t validation" +} + +@test "(nginx-vhosts) proxy:build-config (custom nginx template with deprecated listen http2)" { + run /bin/bash -c "dokku domains:add $TEST_APP www.test.app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP custom_nginx_template_with_http2_listen + echo "output: $output" + echo "status: $status" + assert_success + + assert_output_contains "Deprecated: Usage of 'listen ... http2' within nginx.conf.sigil templates is deprecated" -1 +} + +custom_nginx_template_with_http2_listen() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mkdir -p "$APP_REPO_DIR" + + echo "injecting custom_nginx_template_with_http2_listen -> $APP_REPO_DIR/nginx.conf.sigil" + cat <"$APP_REPO_DIR/nginx.conf.sigil" +{{ range \$port_map := .PROXY_PORT_MAP | split " " }} +{{ \$port_map_list := \$port_map | split ":" }} +{{ \$scheme := index \$port_map_list 0 }} +{{ \$listen_port := index \$port_map_list 1 }} +{{ \$upstream_port := index \$port_map_list 2 }} + +server { + listen [::]:{{ \$listen_port }} http2; + listen {{ \$listen_port }} http2; + server_name {{ $.NOSSL_SERVER_NAME }} customtemplate.${DOKKU_DOMAIN}; + + location / { + proxy_pass http://{{ $.APP }}-{{ \$upstream_port }}; + proxy_http_version 1.1; + proxy_set_header Upgrade \$http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host \$http_host; + proxy_set_header X-Forwarded-Proto \$scheme; + proxy_set_header X-Forwarded-For \$remote_addr; + proxy_set_header X-Forwarded-Port \$server_port; + proxy_set_header X-Request-Start \$msec; + } + include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; +} +{{ end }} + +{{ if $.DOKKU_APP_WEB_LISTENERS }} +{{ range \$upstream_port := $.PROXY_UPSTREAM_PORTS | split " " }} +upstream {{ $.APP }}-{{ \$upstream_port }} { +{{ range \$listeners := $.DOKKU_APP_WEB_LISTENERS | split " " }} +{{ \$listener_list := \$listeners | split ":" }} +{{ \$listener_ip := index \$listener_list 0 }} + server {{ \$listener_ip }}:{{ \$upstream_port }};{{ end }} +} +{{ end }}{{ end }} +EOF + cat "$APP_REPO_DIR/nginx.conf.sigil" } diff --git a/tests/unit/nginx-vhosts_8.bats b/tests/unit/nginx-vhosts_8.bats index 00f202748ac..d5f4f9a74e1 100644 --- a/tests/unit/nginx-vhosts_8.bats +++ b/tests/unit/nginx-vhosts_8.bats @@ -9,20 +9,28 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) nginx:set client-max-body-size" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success - run /bin/bash -c "dokku nginx:set $TEST_APP client-max-body-size" + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "client_max_body_size 1m;" + + run /bin/bash -c "dokku nginx:set $TEST_APP client-max-body-size 2m" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -30,14 +38,14 @@ teardown() { run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" echo "status: $status" - assert_output_contains "client_max_body_size" 0 + assert_output_contains "client_max_body_size 2m;" - run /bin/bash -c "dokku nginx:set $TEST_APP client-max-body-size 1m" + run /bin/bash -c "dokku nginx:set $TEST_APP client-max-body-size" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -45,18 +53,21 @@ teardown() { run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" echo "status: $status" - assert_output_contains "client_max_body_size 1m;" 1 + assert_output_contains "client_max_body_size 1m;" } @test "(nginx-vhosts) nginx:set proxy-read-timeout" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-read-timeout 45s" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -71,7 +82,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -84,14 +95,17 @@ teardown() { @test "(nginx-vhosts) nginx:set proxy-read-timeout (with SSL)" { setup_test_tls - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP proxy-read-timeout 45s" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -106,7 +120,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -116,3 +130,33 @@ teardown() { echo "status: $status" assert_output_contains "45s;" 0 } + +@test "(nginx-vhosts) proxy:build-config ignore bad https mapping" { + setup_test_tls + run deploy_app "dockerfile-noexpose" + echo "output: $output" + echo "status: $status" + assert_output_contains "Ignoring detected https port mapping without an accompanying ssl certificate" 0 + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" + teardown_test_tls + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000" + + run /bin/bash -c "dokku proxy:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + assert_output_contains "Ignoring detected https port mapping without an accompanying ssl certificate" 0 + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000" +} diff --git a/tests/unit/nginx-vhosts_9.bats b/tests/unit/nginx-vhosts_9.bats index 552ba27851f..9c8cedaaedf 100644 --- a/tests/unit/nginx-vhosts_9.bats +++ b/tests/unit/nginx-vhosts_9.bats @@ -9,13 +9,16 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @test "(nginx-vhosts) logging" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run [ -a "/var/log/nginx/$TEST_APP-access.log" ] echo "output: $output" @@ -39,14 +42,17 @@ teardown() { } @test "(nginx-vhosts) log-path" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP access-log-path off" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -54,14 +60,14 @@ teardown() { run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" echo "status: $status" - assert_output_contains "off;" + assert_output_contains "access_log off;" run /bin/bash -c "dokku nginx:set $TEST_APP access-log-path" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -69,14 +75,14 @@ teardown() { run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" echo "status: $status" - assert_output_contains "off;" 0 + assert_output_contains "access_log off;" 0 run /bin/bash -c "dokku nginx:set $TEST_APP error-log-path off" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -84,14 +90,14 @@ teardown() { run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" echo "status: $status" - assert_output_contains "off;" + assert_output_contains "error_log off;" run /bin/bash -c "dokku nginx:set $TEST_APP error-log-path" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -99,18 +105,21 @@ teardown() { run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" echo "status: $status" - assert_output_contains "off;" 0 + assert_output_contains "error_log off;" 0 } @test "(nginx-vhosts) access-log-format" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku nginx:set $TEST_APP access-log-format combined" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -125,7 +134,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -133,7 +142,7 @@ teardown() { run /bin/bash -c "dokku nginx:show-config $TEST_APP" echo "output: $output" echo "status: $status" - assert_output_contains "off;" + assert_output_contains "access_log off;" run /bin/bash -c "dokku nginx:set $TEST_APP access-log-format" run /bin/bash -c "dokku nginx:set $TEST_APP access-log-path" @@ -141,7 +150,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku nginx:build-config $TEST_APP" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -152,22 +161,54 @@ teardown() { assert_output_contains "-access.log;" } -@test "(nginx-vhosts) nginx:build-config (with SSL and unrelated domain)" { +@test "(nginx-vhosts) proxy:build-config (with SSL and unrelated domain)" { setup_test_tls - add_domain "node-js-app.dokku.me" - add_domain "test.dokku.me" - deploy_app - dokku nginx:show-config $TEST_APP - assert_ssl_domain "node-js-app.dokku.me" - assert_http_redirect "http://test.dokku.me" "https://test.dokku.me:443/" + run /bin/bash -c "dokku domains:add $TEST_APP node-js-app.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP test.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "node-js-app.${DOKKU_DOMAIN}" + assert_http_redirect "http://test.${DOKKU_DOMAIN}" "https://test.${DOKKU_DOMAIN}:443/" } -@test "(nginx-vhosts) nginx:build-config (wildcard SSL)" { +@test "(nginx-vhosts) proxy:build-config (wildcard SSL)" { setup_test_tls wildcard - add_domain "wildcard1.dokku.me" - add_domain "wildcard2.dokku.me" - deploy_app - dokku nginx:show-config $TEST_APP - assert_ssl_domain "wildcard1.dokku.me" - assert_ssl_domain "wildcard2.dokku.me" + run /bin/bash -c "dokku domains:add $TEST_APP wildcard1.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP wildcard2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_ssl_domain "wildcard1.${DOKKU_DOMAIN}" + assert_ssl_domain "wildcard2.${DOKKU_DOMAIN}" } diff --git a/tests/unit/nginx-vhosts_properties.bats b/tests/unit/nginx-vhosts_properties.bats new file mode 100644 index 00000000000..586908eec93 --- /dev/null +++ b/tests/unit/nginx-vhosts_properties.bats @@ -0,0 +1,109 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + destroy_app + global_teardown +} + +# assert_nginx_property_set_get verifies that for a given nginx property, +# (a) the raw per-app and global keys cycle through set/unset states, +# (b) the computed key always returns a non-empty value when defaults exist, +# and (c) the computed key falls back through per-app -> global. +# +# This intentionally does not pin the exact default value for properties whose +# default is computed at runtime (e.g. proxy-buffer-size depends on the host's +# page size) - those would be brittle to assert verbatim. The set/unset cycle +# is the canonical test for the report convention. +assert_nginx_property_set_get() { + declare prop="$1" app_value="$2" global_value="$3" + + run /bin/bash -c "dokku nginx:set --global $prop" + assert_success + + run /bin/bash -c "dokku nginx:set --global $prop '$global_value'" + assert_success + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json | jq -r '.\"global-$prop\"'" + assert_success + assert_output "$global_value" + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json | jq -r '.\"computed-$prop\"'" + assert_success + assert_output "$global_value" + + run /bin/bash -c "dokku nginx:set $TEST_APP $prop '$app_value'" + assert_success + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json | jq -r '.\"$prop\"'" + assert_success + assert_output "$app_value" + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json | jq -r '.\"global-$prop\"'" + assert_success + assert_output "$global_value" + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json | jq -r '.\"computed-$prop\"'" + assert_success + assert_output "$app_value" + + run /bin/bash -c "dokku nginx:set $TEST_APP $prop" + assert_success + + run /bin/bash -c "dokku nginx:set --global $prop" + assert_success + + run /bin/bash -c "dokku nginx:report $TEST_APP --format json | jq -r '.\"global-$prop\"'" + assert_success + assert_output "" +} + +@test "(nginx:report) timeout properties raw/global/computed" { + for prop in client-body-timeout client-header-timeout keepalive-timeout lingering-timeout send-timeout proxy-connect-timeout proxy-read-timeout proxy-send-timeout; do + assert_nginx_property_set_get "$prop" "30s" "45s" + done +} + +@test "(nginx:report) buffer properties raw/global/computed" { + assert_nginx_property_set_get "proxy-buffer-size" "8k" "16k" + assert_nginx_property_set_get "proxy-buffering" "off" "on" + assert_nginx_property_set_get "proxy-buffers" "16 16k" "32 16k" + assert_nginx_property_set_get "proxy-busy-buffers-size" "16k" "32k" + assert_nginx_property_set_get "client-max-body-size" "10m" "20m" +} + +@test "(nginx:report) hsts properties raw/global/computed" { + assert_nginx_property_set_get "hsts" "false" "true" + assert_nginx_property_set_get "hsts-include-subdomains" "false" "true" + assert_nginx_property_set_get "hsts-max-age" "3600" "7200" + assert_nginx_property_set_get "hsts-preload" "true" "false" +} + +@test "(nginx:report) x-forwarded properties raw/global/computed" { + assert_nginx_property_set_get "x-forwarded-for-value" "\$remote_addr" "\$proxy_add_x_forwarded_for" + assert_nginx_property_set_get "x-forwarded-port-value" "8080" "8443" + assert_nginx_property_set_get "x-forwarded-proto-value" "https" "http" + assert_nginx_property_set_get "x-forwarded-ssl" "on" "off" +} + +@test "(nginx:report) log/address properties raw/global/computed" { + assert_nginx_property_set_get "bind-address-ipv4" "127.0.0.1" "0.0.0.0" + assert_nginx_property_set_get "bind-address-ipv6" "::1" "::" + assert_nginx_property_set_get "access-log-format" "main" "combined" + assert_nginx_property_set_get "access-log-path" "/var/log/nginx/app-access.log" "/dev/stdout" + assert_nginx_property_set_get "error-log-path" "/var/log/nginx/app-error.log" "/dev/stderr" +} + +@test "(nginx:report) misc properties raw/global/computed" { + assert_nginx_property_set_get "disable-custom-config" "false" "true" + assert_nginx_property_set_get "underscore-in-headers" "on" "off" + assert_nginx_property_set_get "proxy-keepalive" "8" "16" + assert_nginx_property_set_get "nginx-conf-sigil-path" "custom.sigil" "global.sigil" + assert_nginx_property_set_get "nginx-service-command" "/usr/sbin/nginx-app" "/usr/sbin/nginx-global" +} diff --git a/tests/unit/openresty-vhosts.bats b/tests/unit/openresty-vhosts.bats new file mode 100644 index 00000000000..916edcf8ac2 --- /dev/null +++ b/tests/unit/openresty-vhosts.bats @@ -0,0 +1,84 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + create_app +} + +teardown() { + destroy_app + dokku openresty:set --global log-level >/dev/null 2>&1 || true + dokku openresty:set --global proxy-buffer-size >/dev/null 2>&1 || true + dokku openresty:set --global client-max-body-size >/dev/null 2>&1 || true +} + +@test "(openresty:report) info-flag works before deploy" { + run /bin/bash -c "dokku openresty:set --global log-level" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(openresty:report) --format json" { + run /bin/bash -c "dokku openresty:set --global proxy-buffer-size" + assert_success + + run /bin/bash -c "dokku openresty:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"computed-proxy-buffer-size\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "4k" + + run /bin/bash -c "dokku openresty:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global openresty information" +} + +@test "(openresty:report) client-max-body-size raw global computed" { + run /bin/bash -c "dokku openresty:set --global client-max-body-size" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-client-max-body-size" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-max-body-size" + assert_success + assert_output "1m" + + run /bin/bash -c "dokku openresty:set --global client-max-body-size 2m" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-max-body-size" + assert_success + assert_output "2m" + + run /bin/bash -c "dokku openresty:set $TEST_APP client-max-body-size 5m" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-client-max-body-size" + assert_success + assert_output "5m" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-max-body-size" + assert_success + assert_output "5m" +} diff --git a/tests/unit/openresty.bats b/tests/unit/openresty.bats new file mode 100644 index 00000000000..6751438ff17 --- /dev/null +++ b/tests/unit/openresty.bats @@ -0,0 +1,788 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku nginx:stop + dokku openresty:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory + dokku openresty:set --global letsencrypt-email + dokku openresty:start + create_app +} + +teardown() { + dokku openresty:set --global log-level >/dev/null 2>&1 || true + for key in bind-address-ipv6 keepalive-timeout client-body-timeout client-header-timeout client-max-body-size; do + dokku openresty:set --global "$key" >/dev/null || true + done + global_teardown + destroy_app + dokku openresty:stop + dokku nginx:start +} + +@test "(openresty:report) --global --openresty-computed-letsencrypt-server" { + run /bin/bash -c "dokku openresty:report --global --openresty-computed-letsencrypt-server" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "https://acme-staging-v02.api.letsencrypt.org/directory" + + run /bin/bash -c "dokku openresty:report --global --openresty-global-letsencrypt-server" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "https://acme-staging-v02.api.letsencrypt.org/directory" +} + +@test "(openresty:report) --global raw and computed keys in --format json" { + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"global-letsencrypt-server\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "https://acme-staging-v02.api.letsencrypt.org/directory" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"computed-letsencrypt-server\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "https://acme-staging-v02.api.letsencrypt.org/directory" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"global-letsencrypt-email\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"computed-letsencrypt-email\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"global-image\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"computed-image\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"global-allowed-letsencrypt-domains-func-base64\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"computed-allowed-letsencrypt-domains-func-base64\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"global-hsts\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"computed-hsts\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + for key in image letsencrypt-email letsencrypt-server allowed-letsencrypt-domains-func-base64; do + run /bin/bash -c "dokku openresty:report --global --format json | jq -e \"has(\\\"$key\\\")\"" + echo "key: $key" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output "false" + done +} + +@test "(openresty:report) --global global vs computed image" { + run /bin/bash -c "dokku openresty:report --global --openresty-global-image" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --openresty-computed-image" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku openresty:set --global image dokku/openresty-docker-proxy:0.5.6" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report --global --openresty-global-image" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku/openresty-docker-proxy:0.5.6" + + run /bin/bash -c "dokku openresty:report --global --openresty-computed-image" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dokku/openresty-docker-proxy:0.5.6" + + run /bin/bash -c "dokku openresty:set --global image" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report --global --openresty-global-image" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --openresty-computed-image" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists +} + +@test "(openresty:report) --global log-level raw vs computed" { + run /bin/bash -c "dokku openresty:report --global --openresty-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --openresty-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" + + run /bin/bash -c "dokku openresty:set --global log-level DEBUG" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report --global --openresty-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "DEBUG" + + run /bin/bash -c "dokku openresty:report --global --openresty-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "DEBUG" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"global-log-level\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "DEBUG" + + run /bin/bash -c "dokku openresty:report --global --format json | jq -r '.\"computed-log-level\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "DEBUG" + + run /bin/bash -c "dokku openresty:set --global log-level" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report --global --openresty-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report --global --openresty-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" +} + +@test "(openresty:report) per-app raw vs global vs computed for bind-address-ipv6" { + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-bind-address-ipv6" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-global-bind-address-ipv6" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-bind-address-ipv6" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "::" + + run /bin/bash -c "dokku openresty:set $TEST_APP bind-address-ipv6 ::1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-bind-address-ipv6" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "::1" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-bind-address-ipv6" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "::1" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-global-bind-address-ipv6" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:set $TEST_APP bind-address-ipv6" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-bind-address-ipv6" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "::" +} + +@test "(openresty:report) per-app inherits global value, per-app overrides global" { + run /bin/bash -c "dokku openresty:set --global keepalive-timeout 90s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-global-keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "90s" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "90s" + + run /bin/bash -c "dokku openresty:set $TEST_APP keepalive-timeout 30s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "30s" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "30s" + + run /bin/bash -c "dokku openresty:report $TEST_APP --format json | jq -r '.\"keepalive-timeout\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "30s" + + run /bin/bash -c "dokku openresty:report $TEST_APP --format json | jq -r '.\"global-keepalive-timeout\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "90s" + + run /bin/bash -c "dokku openresty:report $TEST_APP --format json | jq -r '.\"computed-keepalive-timeout\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "30s" + + run /bin/bash -c "dokku openresty:set $TEST_APP keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:set --global keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "75s" +} + +@test "(openresty:report) client-header-timeout reads its own property key" { + run /bin/bash -c "dokku openresty:set $TEST_APP client-body-timeout 99s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-header-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "60s" + + run /bin/bash -c "dokku openresty:set $TEST_APP client-header-timeout 30s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-client-header-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "30s" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-header-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "30s" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-body-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "99s" +} + +@test "(openresty:report) client-max-body-size computed default is 1m" { + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-client-max-body-size" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-computed-client-max-body-size" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1m" +} + +@test "(openresty) per-app properties are settable globally" { + run /bin/bash -c "dokku openresty:set --global keepalive-timeout 90s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report --global --openresty-global-keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "90s" + + run /bin/bash -c "dokku openresty:report --global --openresty-computed-keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "90s" + + run /bin/bash -c "dokku openresty:set --global keepalive-timeout" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(openresty) global-only keys" { + for key in allowed-letsencrypt-domains-func-base64 image log-level letsencrypt-email letsencrypt-server; do + run /bin/bash -c "dokku openresty:set $TEST_APP $key somevalue" + echo "key: $key" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" + done + + run /bin/bash -c "dokku openresty:set $TEST_APP bind-address-ipv4 127.0.0.1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:report $TEST_APP --openresty-bind-address-ipv4" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "127.0.0.1" + + run /bin/bash -c "dokku openresty:set $TEST_APP bind-address-ipv4" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(openresty) openresty:help" { + run /bin/bash -c "dokku openresty" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the openresty proxy integration" + help_output="$output" + + run /bin/bash -c "dokku openresty:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the openresty proxy integration" + assert_output "$help_output" +} + +@test "(openresty) single domain" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" +} + +@test "(openresty) multiple domains" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" + assert_http_localhost_response "http" "$TEST_APP-2.dokku.me" "80" "" "python/http.server" +} + +@test "(openresty) ssl" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_http_localhost_response "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:80:5000" + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"openresty.letsencrypt\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku openresty:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"openresty.letsencrypt\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:80:5000 https:443:5000" +} + +@test "(openresty) allowed-domains" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + value="$(echo 'return true' | base64 -w 0)" + run /bin/bash -c "dokku openresty:set --global allowed-letsencrypt-domains-func-base64 $value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker exec openresty-openresty-1 /usr/local/openresty/nginx/sbin/nginx -t" + echo "output: $output" + echo "status: $status" + assert_success + + body='allowed_domains = {"domain.com", "extra-domain.com"} + for index, value in ipairs(allowed_domains) do + if value == domain then + return true + end + end + return false + ' + value="$(echo "$body" | base64 -w 0)" + run /bin/bash -c "dokku openresty:set --global allowed-letsencrypt-domains-func-base64 $value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker exec openresty-openresty-1 /usr/local/openresty/nginx/sbin/nginx -t" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:set --global allowed-letsencrypt-domains-func-base64" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:start" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(openresty) includes" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_openresty_include + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"openresty.include-http-example.conf\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "IyBmb3JjZSB0aGUgY2hhcmFjdGVyIHNldCB0byB1dGYtOApjaGFyc2V0IFVURi04Owo=" + + run /bin/bash -c "docker logs openresty-openresty-1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker exec openresty-openresty-1 /usr/local/openresty/nginx/sbin/nginx -t" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker exec openresty-openresty-1 cat /etc/nginx/sites-enabled/sites.conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "force the character set to utf-8" + assert_output_contains "charset UTF-8;" +} + +@test "(openresty) [security] eval injection via malicious include filename" { + rm -f /tmp/openresty-include + + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_openresty_include_unsafe + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "unsafe filename" -1 + + # No injection payload to test since we're using a simple space character + # The test should have failed during core-post-extract, not during eval +} + +@test "(openresty) label management" { + run /bin/bash -c "dokku proxy:set $TEST_APP openresty" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:labels:add $TEST_APP openresty.directive value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:labels:show $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "openresty.directive=value" + + run /bin/bash -c "dokku openresty:labels:show $TEST_APP openresty.directive" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "value" + + run /bin/bash -c "dokku openresty:labels:show $TEST_APP openresty.directive2" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"openresty.directive\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "value" + + run /bin/bash -c "dokku openresty:labels:remove $TEST_APP openresty.directive" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku openresty:labels:show $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "openresty.directive=value" + + run /bin/bash -c "dokku openresty:labels:show $TEST_APP openresty.directive" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"openresty.directive\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +add_openresty_include() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mkdir -p "$APP_REPO_DIR/openresty/http-includes" + touch "$APP_REPO_DIR/openresty/http-includes/example.conf" + echo "# force the character set to utf-8" >>"$APP_REPO_DIR/openresty/http-includes/example.conf" + echo "charset UTF-8;" >>"$APP_REPO_DIR/openresty/http-includes/example.conf" + + mkdir -p "$APP_REPO_DIR/openresty/http-location-includes" + touch "$APP_REPO_DIR/openresty/http-location-includes/example.conf" + echo "# location-block" >>"$APP_REPO_DIR/openresty/http-location-includes/example.conf" +} + +add_openresty_include_unsafe() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + + mkdir -p "$APP_REPO_DIR/openresty/http-includes" + # Create a filename with a space - simpler test that should be rejected by [^a-zA-Z0-9_.-] + printf 'charset UTF-8;\n' >"$APP_REPO_DIR/openresty/http-includes/unsafe filename.conf" + + mkdir -p "$APP_REPO_DIR/openresty/http-location-includes" + printf '# location\n' >"$APP_REPO_DIR/openresty/http-location-includes/unsafe filename.conf" +} diff --git a/tests/unit/plugin.bats b/tests/unit/plugin.bats index 10761acf84f..c4037754ea6 100644 --- a/tests/unit/plugin.bats +++ b/tests/unit/plugin.bats @@ -1,24 +1,12 @@ #!/usr/bin/env bats load test_helper -TEST_PLUGIN_NAME=smoke-test-plugin -TEST_PLUGIN_GIT_REPO=https://github.com/dokku/${TEST_PLUGIN_NAME}.git -TEST_PLUGIN_LOCAL_REPO="$(mktemp -d)/$TEST_PLUGIN_NAME" - -clone_test_plugin() { - git clone "$TEST_PLUGIN_GIT_REPO" "$TEST_PLUGIN_LOCAL_REPO" -} setup() { global_setup clone_test_plugin } -remove_test_plugin() { - rm -rf $PLUGIN_ENABLED_PATH/$TEST_PLUGIN_NAME $PLUGIN_AVAILABLE_PATH/$TEST_PLUGIN_NAME - rm -rf $TEST_PLUGIN_LOCAL_REPO -} - teardown() { remove_test_plugin || true global_teardown @@ -38,6 +26,70 @@ teardown() { assert_output "$help_output" } +@test "(plugin) plugin:list --format json" { + run /bin/bash -c "dokku plugin:list --format nonsense" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid output format" + + run /bin/bash -c "dokku plugin:list --format json | jq -r '.[] | select(.name == \"apps\") | .core'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku plugin:list --format json | jq -r '.[] | select(.name == \"apps\") | .enabled'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku plugin:list --format json | jq -r '.[] | select(.name == \"apps\") | .source_url'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + # Clone a git-based third-party plugin directly into the available path so the + # git install source is present without running the (build-dependent) install + # trigger. plugn lists it as a disabled plugin. + run /bin/bash -c "git clone $TEST_PLUGIN_GIT_REPO $PLUGIN_AVAILABLE_PATH/$TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku plugin:list --format json | jq -r '.[] | select(.name == \"$TEST_PLUGIN_NAME\") | .source_url'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_PLUGIN_GIT_REPO" + + run /bin/bash -c "dokku plugin:list --format json | jq -r '.[] | select(.name == \"$TEST_PLUGIN_NAME\") | .core'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku plugin:list --format json | jq -r '.[] | select(.name == \"$TEST_PLUGIN_NAME\") | .enabled'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku plugin:list --format json | jq -r '.[] | select(.name == \"$TEST_PLUGIN_NAME\") | .committish | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "40" + + run /bin/bash -c "dokku plugin:list --format json | jq -r '.[] | select(.name == \"$TEST_PLUGIN_NAME\") | .branch'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists +} + @test "(plugin) plugin:install, plugin:disable, plugin:update plugin:uninstall" { run /bin/bash -c "dokku plugin:installed $TEST_PLUGIN_NAME" echo "output: $output" diff --git a/tests/unit/ports.bats b/tests/unit/ports.bats new file mode 100644 index 00000000000..e09440b6000 --- /dev/null +++ b/tests/unit/ports.bats @@ -0,0 +1,417 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + create_app + + dokku --force postgres:destroy $TEST_APP 2>/dev/null || true + dokku --force redis:destroy $TEST_APP 2>/dev/null || true + dokku --force postgres:destroy $TEST_APP-other 2>/dev/null || true + dokku --force redis:destroy $TEST_APP-other 2>/dev/null || true +} + +teardown() { + destroy_app + + dokku --force postgres:destroy $TEST_APP 2>/dev/null || true + dokku --force redis:destroy $TEST_APP 2>/dev/null || true + dokku --force postgres:destroy $TEST_APP-other 2>/dev/null || true + dokku --force redis:destroy $TEST_APP-other 2>/dev/null || true + + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + global_teardown +} + +@test "(ports) ports:help" { + run /bin/bash -c "dokku ports" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage ports for an app" + help_output="$output" + + run /bin/bash -c "dokku ports:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage ports for an app" + assert_output "$help_output" +} + +@test "(ports) ports subcommands (list/add/set/remove/clear)" { + run /bin/bash -c "dokku ports:set $TEST_APP http:1234:5001" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:1234:5001" + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-json | jq -e 'length == 1 and .[0].scheme == \"http\" and .[0].host_port == 1234 and .[0].container_port == 5001'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP http:8080:5002 https:8443:5003" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:1234:5001 http:8080:5002 https:8443:5003" + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-json | jq -e 'length == 3 and any(.scheme == \"http\" and .host_port == 1234 and .container_port == 5001) and any(.scheme == \"http\" and .host_port == 8080 and .container_port == 5002) and any(.scheme == \"https\" and .host_port == 8443 and .container_port == 5003)'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:set $TEST_APP http:8080:5000 https:8443:5000 http:1234:5001" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:set $TEST_APP http:8080:5000 https:8443:5000 http:1234:5001 http:1234:5002" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku ports:add $TEST_APP http:12345:5002 http:12345:5004" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku ports:add $TEST_APP http:1234:5003 http:12345:5004" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku ports:add $TEST_APP http:1234:5001" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:1234:5001 http:8080:5000 https:8443:5000" + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-json | jq -e 'length == 3 and any(.scheme == \"http\" and .host_port == 1234 and .container_port == 5001) and any(.scheme == \"http\" and .host_port == 8080 and .container_port == 5000) and any(.scheme == \"https\" and .host_port == 8443 and .container_port == 5000)'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:remove $TEST_APP 8080" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "http:1234:5001 https:8443:5000" + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-json | jq -e 'length == 2 and any(.scheme == \"http\" and .host_port == 1234 and .container_port == 5001) and any(.scheme == \"https\" and .host_port == 8443 and .container_port == 5000)'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:remove $TEST_APP http:1234:5001" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output "https:8443:5000" + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-json | jq -e 'length == 1 and .[0].scheme == \"https\" and .[0].host_port == 8443 and .[0].container_port == 5000'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:clear $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-json | jq -e '. == [] or . == null'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000" + + run /bin/bash -c "dokku --quiet ports:report $TEST_APP --ports-map-detected-json | jq -e 'length == 1 and .[0].scheme == \"http\" and .[0].host_port == 80 and .[0].container_port == 5000'" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(ports:add) post-deploy add" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP http:8080:5000 http:8081:5000" + echo "output: $output" + echo "status: $status" + assert_success + + URLS="$(dokku --quiet urls "$TEST_APP")" + for URL in $URLS; do + assert_http_success $URL + done + assert_http_success "http://$TEST_APP.${DOKKU_DOMAIN}:8080" + assert_http_success "http://$TEST_APP.${DOKKU_DOMAIN}:8081" +} + +@test "(ports:report) herokuish tls" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000" + + run setup_test_tls + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" +} + +@test "(ports:report) dockerfile tls" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP move_expose_dockerfile_into_place + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:3000:3000 http:3003:3003 udp:3001:3001" + + run setup_test_tls + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "https:3000:3000 https:3003:3003 udp:3001:3001" +} + +@test "(ports:rename) renamed app persists ports" { + if ! dokku plugin:installed postgres; then + run /bin/bash -c "dokku plugin:install https://github.com/dokku/dokku-postgres.git" + echo "output: $output" + echo "status: $status" + assert_success + fi + + if ! dokku plugin:installed redis; then + run /bin/bash -c "dokku plugin:install https://github.com/dokku/dokku-redis.git" + echo "output: $output" + echo "status: $status" + assert_success + fi + + run /bin/bash -c "dokku ports:set $TEST_APP http:80:3000" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP APP_SECRET=REPLACE_WITH_LONG_SECRET_REPLACE_WITH_LONG_SECRET" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku postgres:create $TEST_APP $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku postgres:link $TEST_APP $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku redis:create $TEST_APP $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku redis:link $TEST_APP $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku git:from-image $TEST_APP docmost/docmost:latest + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:80:3000" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:3000:3000" + + run /bin/bash -c "dokku apps:rename $TEST_APP $TEST_APP-other" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Application deployed" + + run /bin/bash -c "dokku ports:report $TEST_APP-other --ports-map" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:80:3000" + + run /bin/bash -c "dokku ports:report $TEST_APP-other --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:3000:3000" +} + +@test "(ports) ports not reinitialized on upgrade" { + run /bin/bash -c "dokku domains:clear-global" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:disable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:add $TEST_APP http:3001:3001" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:disable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:report $TEST_APP --domains-global-enabled" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "false" + + run /bin/bash -c "dokku git:from-image $TEST_APP louislam/uptime-kuma:2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:3001:3001" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:3001:3001" + + run /bin/bash -c "dokku domains:report $TEST_APP --domains-global-enabled" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "false" + + run /bin/bash -c "dokku domains:setup $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Global server virtual host not set, disabling app vhost" + assert_output_not_contains "No port set, setting to random open high port" + assert_output_not_contains "Random port" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:3001:3001" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "http:3001:3001" +} + +@test "(ports:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku ports:report $TEST_APP --format json | jq -r 'has(\"map\") and has(\"ports-map\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ports:report $TEST_APP --format json | jq -r 'has(\"map-detected\") and has(\"ports-map-detected\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/proxied-app.bats b/tests/unit/proxied-app.bats index e16928634dd..92181b2b145 100644 --- a/tests/unit/proxied-app.bats +++ b/tests/unit/proxied-app.bats @@ -29,12 +29,12 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku proxy:ports-set $TEST_APP http:80:8080" + run /bin/bash -c "dokku ports:set $TEST_APP http:80:8080" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/proxy.bats b/tests/unit/proxy.bats index 6b2188961a8..a565383110a 100644 --- a/tests/unit/proxy.bats +++ b/tests/unit/proxy.bats @@ -9,7 +9,9 @@ setup() { } teardown() { - destroy_app 0 $TEST_APP + dokku proxy:set --global proxy-port >/dev/null 2>&1 || true + dokku proxy:set --global proxy-ssl-port >/dev/null 2>&1 || true + destroy_app [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" global_teardown } @@ -28,103 +30,363 @@ teardown() { assert_output "$help_output" } -@test "(proxy) proxy:enable/disable" { - deploy_app - assert_nonssl_domain "${TEST_APP}.dokku.me" +@test "(proxy:report) --global raw vs computed type" { + run /bin/bash -c "dokku proxy:report --global --format json | jq -r '.\"proxy-global-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" - run /bin/bash -c "dokku proxy:disable $TEST_APP" + run /bin/bash -c "dokku proxy:report --global --format json | jq -r '.\"proxy-computed-type\"'" echo "output: $output" echo "status: $status" assert_success + assert_output "nginx" - for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - assert_not_external_port $(< $CID_FILE) - done + run /bin/bash -c "dokku proxy:set --global type caddy" + echo "output: $output" + echo "status: $status" + assert_success - run /bin/bash -c "dokku proxy:enable $TEST_APP" + run /bin/bash -c "dokku proxy:report --global --format json | jq -r '.\"proxy-global-type\"'" echo "output: $output" echo "status: $status" assert_success - assert_http_success "${TEST_APP}.dokku.me" + assert_output "caddy" - for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - assert_not_external_port $(< $CID_FILE) - done + run /bin/bash -c "dokku proxy:report --global --format json | jq -r '.\"proxy-computed-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "caddy" + + run /bin/bash -c "dokku proxy:set --global type" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:report --global --format json | jq -r '.\"proxy-global-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report --global --format json | jq -r '.\"proxy-computed-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "nginx" +} + +@test "(proxy:report) type raw vs computed vs global" { + run /bin/bash -c "dokku proxy:set --global type" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-type\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-type\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-type\"'" + assert_success + assert_output "nginx" + + run /bin/bash -c "dokku proxy:set --global type caddy" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-type\"'" + assert_success + assert_output "caddy" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-type\"'" + assert_success + assert_output "caddy" + + run /bin/bash -c "dokku proxy:set $TEST_APP type traefik" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-type\"'" + assert_success + assert_output "traefik" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-type\"'" + assert_success + assert_output "caddy" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-type\"'" + assert_success + assert_output "traefik" + + run /bin/bash -c "dokku proxy:set $TEST_APP type" + assert_success + + run /bin/bash -c "dokku proxy:set --global type" + assert_success +} + +@test "(proxy:report) proxy-port raw vs computed vs global" { + run /bin/bash -c "dokku proxy:set --global proxy-port" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP proxy-port" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-proxy-port\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-proxy-port\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-proxy-port\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:set --global proxy-port 5000" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-proxy-port\"'" + assert_success + assert_output "5000" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-proxy-port\"'" + assert_success + assert_output "5000" + + run /bin/bash -c "dokku proxy:set $TEST_APP proxy-port 6000" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-proxy-port\"'" + assert_success + assert_output "6000" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-proxy-port\"'" + assert_success + assert_output "5000" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-proxy-port\"'" + assert_success + assert_output "6000" + + run /bin/bash -c "dokku proxy:set $TEST_APP proxy-port" + assert_success + + run /bin/bash -c "dokku proxy:set --global proxy-port" + assert_success +} + +@test "(proxy:report) proxy-ssl-port raw vs computed vs global" { + run /bin/bash -c "dokku proxy:set --global proxy-ssl-port" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP proxy-ssl-port" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-proxy-ssl-port\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-proxy-ssl-port\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-proxy-ssl-port\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:set --global proxy-ssl-port 5443" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-proxy-ssl-port\"'" + assert_success + assert_output "5443" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-proxy-ssl-port\"'" + assert_success + assert_output "5443" + + run /bin/bash -c "dokku proxy:set $TEST_APP proxy-ssl-port 6443" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-proxy-ssl-port\"'" + assert_success + assert_output "6443" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-global-proxy-ssl-port\"'" + assert_success + assert_output "5443" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-proxy-ssl-port\"'" + assert_success + assert_output "6443" + + run /bin/bash -c "dokku proxy:set $TEST_APP proxy-ssl-port" + assert_success + + run /bin/bash -c "dokku proxy:set --global proxy-ssl-port" + assert_success } -@test "(proxy) proxy:ports (list/add/set/remove/clear)" { - run /bin/bash -c "dokku proxy:ports-set $TEST_APP http:1234:5001" +@test "(proxy:report) disabled raw and computed match disabled state" { + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-disabled\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-disabled\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-enabled\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:disable $TEST_APP" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-disabled\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-disabled\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-enabled\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku proxy:enable $TEST_APP" + assert_success + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-disabled\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-computed-disabled\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r '.\"proxy-enabled\"'" + assert_success + assert_output "true" +} + +@test "(proxy:set) invalid port mapping set" { + run /bin/bash -c "dokku proxy:set $TEST_APP http:80:80" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Detected potential port mapping instead of proxy type" +} + +@test "(proxy) proxy:build-config/clear-config" { + run deploy_app echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" echo "output: $output" echo "status: $status" - assert_output "http 1234 5001" + assert_success - run /bin/bash -c "dokku proxy:ports-add $TEST_APP http:8080:5002 https:8443:5003" + run /bin/bash -c "rm -f $DOKKU_ROOT/$TEST_APP/nginx.conf" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" echo "output: $output" echo "status: $status" - assert_output "http 1234 5001 http 8080 5002 https 8443 5003" + assert_failure - run /bin/bash -c "dokku proxy:ports-set $TEST_APP http:8080:5000 https:8443:5000 http:1234:5001" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" echo "output: $output" echo "status: $status" - assert_output "http 1234 5001 http 8080 5000 https 8443 5000" + assert_success - run /bin/bash -c "dokku proxy:ports-remove $TEST_APP 8080" + run /bin/bash -c "dokku proxy:clear-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" echo "output: $output" echo "status: $status" - assert_output "http 1234 5001 https 8443 5000" + assert_failure - run /bin/bash -c "dokku proxy:ports-remove $TEST_APP http:1234:5001" + run /bin/bash -c "dokku proxy:build-config $TEST_APP" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" echo "output: $output" echo "status: $status" - assert_output "https 8443 5000" + assert_success - run /bin/bash -c "dokku proxy:ports-clear $TEST_APP" + run /bin/bash -c "dokku proxy:clear-config --all" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf" echo "output: $output" echo "status: $status" - assert_output "http 80 5000" + assert_failure } -@test "(proxy) proxy:ports (post-deploy add)" { - deploy_app - run /bin/bash -c "dokku proxy:ports-add $TEST_APP http:8080:5000 http:8081:5000" +@test "(proxy) proxy:enable/disable" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + assert_nonssl_domain "${TEST_APP}.${DOKKU_DOMAIN}" + + run /bin/bash -c "dokku proxy:disable $TEST_APP" echo "output: $output" echo "status: $status" assert_success - URLS="$(dokku --quiet urls "$TEST_APP")" - for URL in $URLS; do - assert_http_success $URL + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + assert_not_external_port $(<$CID_FILE) done - assert_http_success "http://$TEST_APP.dokku.me:8080" - assert_http_success "http://$TEST_APP.dokku.me:8081" + + run /bin/bash -c "dokku proxy:enable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_http_success "${TEST_APP}.${DOKKU_DOMAIN}" + + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + assert_not_external_port $(<$CID_FILE) + done +} + +@test "(proxy:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r 'has(\"type\") and has(\"proxy-type\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r 'has(\"global-type\") and has(\"proxy-global-type\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r 'has(\"computed-type\") and has(\"proxy-computed-type\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku proxy:report $TEST_APP --format json | jq -r 'has(\"enabled\") and has(\"proxy-enabled\")'" + assert_success + assert_output "true" } diff --git a/tests/unit/ps-cnb.bats b/tests/unit/ps-cnb.bats deleted file mode 100644 index 5abbbb1bd82..00000000000 --- a/tests/unit/ps-cnb.bats +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bats - -load test_helper - -setup_file() { - install_pack -} - -setup() { - global_setup - create_app -} - -teardown() { - destroy_app - global_teardown -} - -@test "(ps) cnb env vars" { - run /bin/bash -c "dokku config:set $TEST_APP DOKKU_CNB_EXPERIMENTAL=1" - echo "output: $output" - echo "status: $status" - assert_success - - run deploy_app python dokku@dokku.me:$TEST_APP add_requirements_txt - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "curl $(dokku url $TEST_APP)/env" - echo "output: $output" - echo "status: $status" - assert_success - assert_output_contains '"DOKKU_CNB_EXPERIMENTAL": "1"' -} diff --git a/tests/unit/ps-dockerfile-1.bats b/tests/unit/ps-dockerfile-1.bats index bba89ed3072..a7bd6645deb 100644 --- a/tests/unit/ps-dockerfile-1.bats +++ b/tests/unit/ps-dockerfile-1.bats @@ -13,13 +13,18 @@ teardown() { } @test "(ps) dockerfile" { - deploy_app dockerfile + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:stop $TEST_APP" echo "output: $output" echo "status: $status" assert_success + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_failure @@ -30,7 +35,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -41,7 +46,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -52,7 +57,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -65,9 +70,21 @@ teardown() { echo "status: $status" assert_success - destroy_app - create_app - deploy_app dockerfile-procfile + run destroy_app + echo "output: $output" + echo "status: $status" + assert_success + + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile-procfile + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:scale $TEST_APP non-existent=2" echo "output: $output" echo "status: $status" @@ -80,10 +97,14 @@ teardown() { echo "status: $status" assert_success - deploy_app dockerfile + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + CIDS="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - CIDS+=$(< $CID_FILE) + CIDS+=$(<$CID_FILE) CIDS+=" " done CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") @@ -98,7 +119,7 @@ teardown() { assert_success CIDS="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - CIDS+=$(< $CID_FILE) + CIDS+=$(<$CID_FILE) CIDS+=" " done CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") @@ -114,7 +135,7 @@ teardown() { CIDS="" shopt -s nullglob for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - CIDS+=$(< $CID_FILE) + CIDS+=$(<$CID_FILE) CIDS+=" " done run /bin/bash -c "[[ -z \"$CIDS\" ]]" diff --git a/tests/unit/ps-dockerfile-2.bats b/tests/unit/ps-dockerfile-2.bats index 35dae977fb1..7a26fe21254 100644 --- a/tests/unit/ps-dockerfile-2.bats +++ b/tests/unit/ps-dockerfile-2.bats @@ -12,16 +12,20 @@ teardown() { global_teardown } -@test "(ps:scale) dockerfile dokku-scale" { +@test "(ps:scale) dockerfile app-json formations" { run /bin/bash -c "dokku ps:scale $TEST_APP web=2" echo "output: $output" echo "status: $status" assert_success - deploy_app dockerfile-dokku-scale + run deploy_app dockerfile-app-json-formations + echo "output: $output" + echo "status: $status" + assert_success + CIDS="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - CIDS+=$(< $CID_FILE) + CIDS+=$(<$CID_FILE) CIDS+=" " done CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") @@ -42,13 +46,17 @@ teardown() { } @test "(ps) dockerfile with procfile" { - deploy_app dockerfile-procfile + run deploy_app dockerfile-procfile + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:stop $TEST_APP" echo "output: $output" echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_failure @@ -59,7 +67,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -70,7 +78,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -81,7 +89,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -106,7 +114,11 @@ teardown() { echo "status: $status" assert_success - deploy_app dockerfile-procfile + run deploy_app dockerfile-procfile + echo "output: $output" + echo "status: $status" + assert_success + for PROC_TYPE in web worker; do run /bin/bash -c "docker ps --format '{{.ID}} {{.Command}}' --no-trunc" echo "output: $output" @@ -114,7 +126,7 @@ teardown() { assert_success goodlines="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do - cid=$(< $CID_FILE) + cid=$(<$CID_FILE) assert_output_contains "$cid" goodlines+=$(echo "$output" | grep "$cid") done @@ -133,7 +145,7 @@ teardown() { assert_success goodlines="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do - cid=$(< $CID_FILE) + cid=$(<$CID_FILE) assert_output_contains "$cid" goodlines+=$(echo "$output" | grep "$cid") done @@ -149,7 +161,7 @@ teardown() { CIDS="" shopt -s nullglob for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do - CIDS+=$(< $CID_FILE) + CIDS+=$(<$CID_FILE) CIDS+=" " done shopt -u nullglob @@ -159,3 +171,18 @@ teardown() { assert_success done } + +@test "(ps:scale) dockerfile unchanged process" { + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP worker=2" + echo "output: $output" + echo "status: $status" + assert_output_contains "Scaling $TEST_APP processes: worker=2" + assert_output_contains "Deploying worker" + assert_output_contains "Deploying web" 0 + assert_success +} diff --git a/tests/unit/ps-general-1.bats b/tests/unit/ps-general-1.bats new file mode 100644 index 00000000000..20a00858df2 --- /dev/null +++ b/tests/unit/ps-general-1.bats @@ -0,0 +1,623 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + destroy_app + global_teardown +} + +@test "(ps) ps:help" { + run /bin/bash -c "dokku ps" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage app processes" + help_output="$output" + + run /bin/bash -c "dokku ps:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage app processes" + assert_output "$help_output" +} + +@test "(ps) ps:inspect" { + run /bin/bash -c "dokku config:set $TEST_APP key=value key=value=value" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$CID" 7 +} + +@test "(ps:set) procfile" { + run /bin/bash -c "dokku ps:set --global procfile-path .dokku/Procfile" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-computed-procfile-path" + echo "output: $output" + echo "status: $status" + assert_output ".dokku/Procfile" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-global-procfile-path" + echo "output: $output" + echo "status: $status" + assert_output ".dokku/Procfile" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-procfile-path" + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run /bin/bash -c "dokku ps:set --global procfile-path" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-computed-procfile-path" + echo "output: $output" + echo "status: $status" + assert_output "Procfile" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-global-procfile-path" + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-procfile-path" + echo "output: $output" + echo "status: $status" + assert_output "" + assert_success +} + +@test "(ps:set) skip-deploy raw/global/computed" { + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-skip-deploy\"'" + echo "output: $output" + echo "status: $status" + assert_output "false" + assert_success + + run /bin/bash -c "dokku ps:set --global skip-deploy true" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-skip-deploy\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-skip-deploy\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-skip-deploy\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:set $TEST_APP skip-deploy false" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-skip-deploy\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-skip-deploy\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-skip-deploy\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku ps:set $TEST_APP skip-deploy" + assert_success + + run /bin/bash -c "dokku ps:set --global skip-deploy" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-skip-deploy\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-skip-deploy\"'" + assert_success + assert_output "false" +} + +@test "(ps:set) dockerfile-start-cmd raw and computed" { + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-dockerfile-start-cmd\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-dockerfile-start-cmd\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:set $TEST_APP dockerfile-start-cmd 'node app.js'" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-dockerfile-start-cmd\"'" + assert_success + assert_output "node app.js" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-dockerfile-start-cmd\"'" + assert_success + assert_output "node app.js" + + run /bin/bash -c "dokku ps:set $TEST_APP dockerfile-start-cmd" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-dockerfile-start-cmd\"'" + assert_success + assert_output "" +} + +@test "(ps:set) start-cmd raw and computed" { + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-start-cmd\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-start-cmd\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:set $TEST_APP start-cmd 'bundle exec rails server'" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-start-cmd\"'" + assert_success + assert_output "bundle exec rails server" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-start-cmd\"'" + assert_success + assert_output "bundle exec rails server" + + run /bin/bash -c "dokku ps:set $TEST_APP start-cmd" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-start-cmd\"'" + assert_success + assert_output "" +} + +@test "(ps:scale) procfile commands extraction" { + source "$PLUGIN_CORE_AVAILABLE_PATH/ps/functions" + cat <"$DOKKU_LIB_ROOT/data/ps/$TEST_APP/Procfile" +web: node web.js --port \$PORT +worker: node worker.js +release: interpolated \$ENV_VAR +EOF + + run /bin/bash -c "dokku config:set '$TEST_APP' ENV_VAR=value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku plugin:trigger procfile-get-command '$TEST_APP' web 5001" + echo "output: $output" + echo "status: $status" + assert_output "node web.js --port 5001" + + run /bin/bash -c "dokku plugin:trigger procfile-get-command '$TEST_APP' worker" + echo "output: $output" + echo "status: $status" + assert_output "node worker.js" + + run /bin/bash -c "dokku plugin:trigger procfile-get-command '$TEST_APP' release 5001" + echo "output: $output" + echo "status: $status" + assert_output "interpolated value" +} + +@test "(ps:scale) update formations from Procfile" { + local TMP=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX") + trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM + + CUSTOM_TMP="$TMP" deploy_app nodejs-express + + run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" + output=$(echo "$output" | tr -s " ") + echo "output: ($output)" + echo "status: $status" + assert_output $'cron: 0\ncustom: 0\nrelease: 0\nweb: 1\nworker: 0' + + pushd $TMP + echo scaletest: sleep infinity >>Procfile + git commit Procfile -m 'Add scaletest process' + git push target master:master + + run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" + output=$(echo "$output" | tr -s " ") + echo "output: ($output)" + echo "status: $status" + assert_output $'cron: 0\ncustom: 0\nrelease: 0\nscaletest: 0\nweb: 1\nworker: 0' + + popd + rm -rf "$TMP" +} + +@test "(ps:scale) ps:scale formatting" { + echo "web=4 +worker=1 +beat =0 +web =0" >/var/lib/dokku/config/ps/$TEST_APP/scale + + run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output $'beat: 0\nweb: 4\nworker: 1' +} + +@test "(ps:scale) --format json" { + run /bin/bash -c "dokku ps:scale $TEST_APP --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output '[{"process_type":"web","quantity":1}]' + + echo "web=4 +worker=1" >/var/lib/dokku/config/ps/$TEST_APP/scale + + run /bin/bash -c "dokku ps:scale $TEST_APP --format json | jq '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" + + run /bin/bash -c "dokku ps:scale $TEST_APP --format json | jq -r '.[] | select(.process_type==\"web\") | .quantity'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "4" + + run /bin/bash -c "dokku ps:scale $TEST_APP --format json | jq -r '.[] | select(.process_type==\"worker\") | .quantity'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + rm -f /var/lib/dokku/config/ps/$TEST_APP/scale + run /bin/bash -c "dokku ps:scale $TEST_APP --format json" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "[]" +} + +@test "(ps) handle windows newlines in procfile" { + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP procfile_line_endings_to_windows + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(ps:report) stop-timeout-seconds raw vs computed vs global" { + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-stop-timeout-seconds\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"stop-timeout-seconds\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"global-stop-timeout-seconds\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" + assert_success + assert_output "30" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "30" + + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds 90" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" + assert_success + assert_output "90" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"global-stop-timeout-seconds\"'" + assert_success + assert_output "90" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" + assert_success + assert_output "90" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "90" + + run /bin/bash -c "dokku ps:set $TEST_APP stop-timeout-seconds 45" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-stop-timeout-seconds\"'" + assert_success + assert_output "45" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"stop-timeout-seconds\"'" + assert_success + assert_output "45" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" + assert_success + assert_output "90" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" + assert_success + assert_output "45" + + run /bin/bash -c "dokku ps:report $TEST_APP --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "45" + + run /bin/bash -c "dokku ps:set $TEST_APP stop-timeout-seconds" + assert_success + + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds" + assert_success +} + +@test "(ps:report) --global --format json" { + run /bin/bash -c "dokku ps:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"ps-global-procfile-path\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"global-procfile-path\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"ps-computed-procfile-path\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"computed-procfile-path\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"ps-global-stop-timeout-seconds\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"global-stop-timeout-seconds\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"ps-computed-stop-timeout-seconds\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r 'has(\"computed-stop-timeout-seconds\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds 75" + assert_success + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" + assert_success + assert_output "75" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"global-stop-timeout-seconds\"'" + assert_success + assert_output "75" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" + assert_success + assert_output "75" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "75" + + run /bin/bash -c "dokku ps:set --global stop-timeout-seconds" + assert_success + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"ps-global-stop-timeout-seconds\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"global-stop-timeout-seconds\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"ps-computed-stop-timeout-seconds\"'" + assert_success + assert_output "30" + + run /bin/bash -c "dokku ps:report --global --format json | jq -r '.\"computed-stop-timeout-seconds\"'" + assert_success + assert_output "30" +} + +@test "(ps:restart-policy) default policy" { + run /bin/bash -c "dokku ps:report $TEST_APP --ps-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-computed-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "on-failure:10" +} + +@test "(ps:restart-policy) ps:set restart-policy" { + for policy in no unless-stopped always on-failure on-failure:20; do + run /bin/bash -c "dokku ps:set $TEST_APP restart-policy $policy" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "$policy" + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-computed-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "$policy" + done +} + +@test "(ps:restart-policy) ps:set restart-policy unset" { + run /bin/bash -c "dokku ps:set $TEST_APP restart-policy on-failure:5" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "on-failure:5" + + run /bin/bash -c "dokku ps:set $TEST_APP restart-policy" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-computed-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "on-failure:10" +} + +@test "(ps:restart-policy) invalid policy" { + run /bin/bash -c "dokku ps:set $TEST_APP restart-policy bogus" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid restart-policy specified" +} + +@test "(ps:restart-policy) global policy" { + run /bin/bash -c "dokku ps:set --global restart-policy always" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-global-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "always" + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-computed-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "always" + + run /bin/bash -c "dokku ps:set $TEST_APP restart-policy no" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-computed-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "no" + + run /bin/bash -c "dokku ps:set --global restart-policy" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(ps:restart-policy) deployed policy" { + test_restart_policy="on-failure:20" + run /bin/bash -c "dokku ps:set $TEST_APP restart-policy $test_restart_policy" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-restart-policy" + echo "output: $output" + echo "status: $status" + assert_output "$test_restart_policy" + + run deploy_app dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect $CID" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect -f '{{ .HostConfig.RestartPolicy.Name }}:{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "$test_restart_policy" + + run /bin/bash -c "dokku ps:set $TEST_APP restart-policy" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect -f '{{ .HostConfig.RestartPolicy.Name }}:{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "on-failure:10" +} + +procfile_line_endings_to_windows() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + echo "setting line endings on Procfile to \n via unix2dos" + sed -i -e 's/\r*$/\r/' "$APP_REPO_DIR/Procfile" +} diff --git a/tests/unit/ps-general.bats b/tests/unit/ps-general-2.bats similarity index 56% rename from tests/unit/ps-general.bats rename to tests/unit/ps-general-2.bats index 7c8a5bc3469..db2520b3b7c 100644 --- a/tests/unit/ps-general.bats +++ b/tests/unit/ps-general-2.bats @@ -12,116 +12,6 @@ teardown() { global_teardown } -@test "(ps) ps:help" { - run /bin/bash -c "dokku ps" - echo "output: $output" - echo "status: $status" - assert_output_contains "Manage app processes" - help_output="$output" - - run /bin/bash -c "dokku ps:help" - echo "output: $output" - echo "status: $status" - assert_output_contains "Manage app processes" - assert_output "$help_output" -} - -@test "(ps) ps:inspect" { - dokku config:set "$TEST_APP" key=value key=value=value - deploy_app dockerfile - - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run /bin/bash -c "dokku ps:inspect $TEST_APP" - echo "output: $output" - echo "status: $status" - assert_success - assert_output_contains "$CID" 6 -} - -@test "(ps:scale) procfile commands extraction" { - source "$PLUGIN_CORE_AVAILABLE_PATH/ps/functions" - cat < "$DOKKU_LIB_ROOT/data/ps/$TEST_APP/Procfile" -web: node web.js --port \$PORT -worker: node worker.js -EOF - PLUGIN_PATH=/var/lib/dokku/plugins PLUGIN_CORE_AVAILABLE_PATH=/var/lib/dokku/core-plugins/available DOKKU_LIB_ROOT=/var/lib/dokku run plugn trigger procfile-get-command "$TEST_APP" web 5001 - echo "output: $output" - echo "status: $status" - assert_output "node web.js --port 5001" - - PLUGIN_PATH=/var/lib/dokku/plugins PLUGIN_CORE_AVAILABLE_PATH=/var/lib/dokku/core-plugins/available DOKKU_LIB_ROOT=/var/lib/dokku run plugn trigger procfile-get-command "$TEST_APP" worker - echo "output: $output" - echo "status: $status" - assert_output "node worker.js" -} - -@test "(ps:scale) update formations from Procfile" { - local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") - trap 'popd &>/dev/null || true; rm -rf "$TMP"' INT TERM - - CUSTOM_TMP="$TMP" deploy_app nodejs-express - - run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" - output=$(echo "$output" | tr -s " ") - echo "output: ($output)" - assert_output $'cron: 0\ncustom: 0\nrelease: 0\nweb: 1\nworker: 0' - - pushd $TMP - echo scaletest: sleep infinity >> Procfile - git commit Procfile -m 'Add scaletest process' - git push target master:master - - run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" - output=$(echo "$output" | tr -s " ") - echo "output: ($output)" - assert_output $'cron: 0\ncustom: 0\nrelease: 0\nscaletest: 0\nweb: 1\nworker: 0' - - popd - rm -rf "$TMP" -} - -@test "(ps:restart-policy) default policy" { - run /bin/bash -c "dokku --quiet ps:report $TEST_APP --ps-restart-policy" - echo "output: $output" - echo "status: $status" - assert_output "on-failure:10" -} - -@test "(ps:restart-policy) ps:set restart-policy" { - for policy in no unless-stopped always on-failure on-failure:20; do - run /bin/bash -c "dokku ps:set $TEST_APP restart-policy $policy" - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku --quiet ps:report $TEST_APP --ps-restart-policy" - echo "output: $output" - echo "status: $status" - assert_output "$policy" - done -} - -@test "(ps:restart-policy) deployed policy" { - test_restart_policy="on-failure:20" - run /bin/bash -c "dokku ps:set $TEST_APP restart-policy $test_restart_policy" - echo "output: $output" - echo "status: $status" - assert_success - - run /bin/bash -c "dokku --quiet ps:report $TEST_APP --ps-restart-policy" - echo "output: $output" - echo "status: $status" - assert_output "$test_restart_policy" - - deploy_app dockerfile - - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) - run /bin/bash -c "docker inspect -f '{{ .HostConfig.RestartPolicy.Name }}:{{ .HostConfig.RestartPolicy.MaximumRetryCount }}' $CID" - echo "output: $output" - echo "status: $status" - assert_output "$test_restart_policy" -} - @test "(ps:rebuild) old app name" { run /bin/bash -c "dokku --force apps:destroy $TEST_APP" echo "output: $output" @@ -199,13 +89,19 @@ EOF assert_output_contains "bar" } - @test "(ps:set) procfile-path" { - run deploy_app dockerfile-procfile + run deploy_app python echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Arg: web.py" + assert_output_contains "Arg: first.Procfile" + assert_success + run /bin/bash -c "dokku ps:set $TEST_APP procfile-path nonexistent-procfile" echo "output: $output" echo "status: $status" @@ -215,7 +111,7 @@ EOF echo "output: $output" echo "status: $status" assert_failure - assert_output_contains "Could not start due to" + assert_output_contains "Could not start due to" 2 run /bin/bash -c "dokku ps:set $TEST_APP procfile-path second.Procfile" echo "output: $output" @@ -228,6 +124,13 @@ EOF assert_success assert_output_contains 'SECRET_KEY:' 0 + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Arg: web.py" + assert_output_contains "Arg: second.Procfile" + assert_success + run /bin/bash -c "dokku ps:set $TEST_APP procfile-path" echo "output: $output" echo "status: $status" @@ -238,4 +141,58 @@ EOF echo "status: $status" assert_success assert_output_contains 'SECRET_KEY:' + + run /bin/bash -c "dokku logs $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "Arg: web.py" + assert_output_contains "Arg: first.Procfile" + assert_success +} + +@test "(ps:scale) remove zerod processes" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP worker=1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" + output=$(echo "$output" | tr -s " ") + echo "output: ($output)" + assert_output $'cron: 0\ncustom: 0\nrelease: 0\ntask: 0\nweb: 1\nworker: 1' + + run /bin/bash -c "dokku ps:scale $TEST_APP worker=0" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" + output=$(echo "$output" | tr -s " ") + echo "output: ($output)" + assert_output $'cron: 0\ncustom: 0\nrelease: 0\ntask: 0\nweb: 1\nworker: 0' + + run /bin/bash -c "dokku ps:set $TEST_APP procfile-path second.Procfile" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet ps:scale $TEST_APP" + output=$(echo "$output" | tr -s " ") + echo "output: ($output)" + assert_output 'web: 1' } diff --git a/tests/unit/ps-herokuish-1.bats b/tests/unit/ps-herokuish-1.bats index 10178ca2100..31ecd2c651b 100644 --- a/tests/unit/ps-herokuish-1.bats +++ b/tests/unit/ps-herokuish-1.bats @@ -13,13 +13,17 @@ teardown() { } @test "(ps) herokuish" { - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ps:stop $TEST_APP" echo "output: $output" echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_failure @@ -30,7 +34,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -41,7 +45,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success @@ -52,7 +56,7 @@ teardown() { echo "status: $status" assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do - run /bin/bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + run /bin/bash -c "docker ps -q --no-trunc | grep -q $(<$CID_FILE)" echo "output: $output" echo "status: $status" assert_success diff --git a/tests/unit/ps-herokuish-2.bats b/tests/unit/ps-herokuish-2.bats index 0dc232717f3..cab55128fe5 100644 --- a/tests/unit/ps-herokuish-2.bats +++ b/tests/unit/ps-herokuish-2.bats @@ -19,11 +19,14 @@ teardown() { echo "status: $status" assert_success - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success for PROC_TYPE in web worker; do CIDS="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do - CIDS+=$(< $CID_FILE) + CIDS+=$(<$CID_FILE) CIDS+=" " done CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") @@ -40,7 +43,7 @@ teardown() { for PROC_TYPE in web worker; do CIDS="" for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do - CIDS+=$(< $CID_FILE) + CIDS+=$(<$CID_FILE) CIDS+=" " done CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") @@ -58,7 +61,7 @@ teardown() { CIDS="" shopt -s nullglob for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do - CIDS+=$(< $CID_FILE) + CIDS+=$(<$CID_FILE) CIDS+=" " done shopt -u nullglob @@ -71,8 +74,15 @@ teardown() { @test "(ps:restore) herokuish" { MYAPP="manual-randomtestapp-1" - create_app "$MYAPP" - deploy_app + run create_app "$MYAPP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku ps:stop $TEST_APP" echo "output: $output" diff --git a/tests/unit/registry.bats b/tests/unit/registry.bats index 67bb6adf370..249676752b3 100644 --- a/tests/unit/registry.bats +++ b/tests/unit/registry.bats @@ -5,7 +5,7 @@ load test_helper setup() { global_setup create_app - dokku config:set $TEST_APP DOKKU_WAIT_TO_RETIRE=30 + dokku checks:set $TEST_APP wait-to-retire 30 } teardown() { @@ -27,7 +27,7 @@ teardown() { assert_output "$help_output" } -@test "(registry) registry:login" { +@test "(registry:login) global login with deprecated warning" { if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" fi @@ -37,6 +37,7 @@ teardown() { echo "status: $status" assert_success assert_output_contains "Login Succeeded" + assert_output_contains "Deprecated: please use --global flag" run /bin/bash -c "echo $DOCKERHUB_TOKEN | dokku registry:login docker.io --password-stdin $DOCKERHUB_USERNAME" echo "output: $output" @@ -45,6 +46,401 @@ teardown() { assert_output_contains "Login Succeeded" } +@test "(registry:login) global login with --global flag" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + run /bin/bash -c "dokku registry:login --global docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Login Succeeded" + assert_output_not_contains "Deprecated" +} + +@test "(registry:login) per-app login" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + run /bin/bash -c "dokku registry:login $TEST_APP docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Login Succeeded" + + run /bin/bash -c "test -f /var/lib/dokku/config/registry/$TEST_APP/config.json" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(registry:logout) per-app logout" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + run /bin/bash -c "dokku registry:login $TEST_APP docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:logout $TEST_APP docker.io" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(registry:logout) global logout" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + run /bin/bash -c "dokku registry:login --global docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:logout --global docker.io" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(registry) per-app credentials deleted on app destroy" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + run /bin/bash -c "dokku registry:login $TEST_APP docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -d /var/lib/dokku/config/registry/$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + destroy_app + + run /bin/bash -c "test -d /var/lib/dokku/config/registry/$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + + create_app +} + +@test "(registry:report) image-repo raw vs computed vs global" { + run /bin/bash -c "dokku registry:set --global image-repo-template" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-image-repo\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-image-repo-template\"'" + assert_success + assert_output "" + + # When neither per-app image-repo nor a global image-repo-template are set, + # reportComputedImageRepo falls back to common.GetAppImageRepo which returns + # the default dokku/ image name. + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo\"'" + assert_success + assert_output "dokku/$TEST_APP" + + run /bin/bash -c "dokku registry:set --global image-repo-template 'dokku/{{ APP }}'" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-image-repo-template\"'" + assert_success + assert_output 'dokku/{{ APP }}' + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo\"'" + assert_success + assert_output "dokku/$TEST_APP" + + run /bin/bash -c "dokku registry:set $TEST_APP image-repo my/$TEST_APP" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-image-repo\"'" + assert_success + assert_output "my/$TEST_APP" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo\"'" + assert_success + assert_output "my/$TEST_APP" + + run /bin/bash -c "dokku registry:set $TEST_APP image-repo" + assert_success + + run /bin/bash -c "dokku registry:set --global image-repo-template" + assert_success +} + +@test "(registry:report) image-repo-template raw vs computed vs global" { + run /bin/bash -c "dokku registry:set --global image-repo-template" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-image-repo-template\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-image-repo-template\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo-template\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:set --global image-repo-template 'global-prefix/{{ .AppName }}'" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-image-repo-template\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-image-repo-template\"'" + assert_success + assert_output 'global-prefix/{{ .AppName }}' + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo-template\"'" + assert_success + assert_output 'global-prefix/{{ .AppName }}' + + # The global template renders into the computed image-repo. + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo\"'" + assert_success + assert_output "global-prefix/$TEST_APP" + + # Per-app template overrides the global template. + run /bin/bash -c "dokku registry:set $TEST_APP image-repo-template 'app-prefix/{{ .AppName }}-prod'" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-image-repo-template\"'" + assert_success + assert_output 'app-prefix/{{ .AppName }}-prod' + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-image-repo-template\"'" + assert_success + assert_output 'global-prefix/{{ .AppName }}' + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo-template\"'" + assert_success + assert_output 'app-prefix/{{ .AppName }}-prod' + + # The per-app template flows through to the computed image-repo. + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo\"'" + assert_success + assert_output "app-prefix/$TEST_APP-prod" + + # Unsetting the per-app template restores the global template behavior. + run /bin/bash -c "dokku registry:set $TEST_APP image-repo-template" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-image-repo-template\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo-template\"'" + assert_success + assert_output 'global-prefix/{{ .AppName }}' + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-image-repo\"'" + assert_success + assert_output "global-prefix/$TEST_APP" + + run /bin/bash -c "dokku registry:set --global image-repo-template" + assert_success +} + +@test "(registry:report) push-on-release raw vs computed vs global" { + run /bin/bash -c "dokku registry:set --global push-on-release" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-push-on-release\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-push-on-release\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-push-on-release\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku registry:set --global push-on-release true" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-push-on-release\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-push-on-release\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:set $TEST_APP push-on-release false" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-push-on-release\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-push-on-release\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku registry:set $TEST_APP push-on-release" + assert_success + + run /bin/bash -c "dokku registry:set --global push-on-release" + assert_success +} + +@test "(registry:report --global) computed and global keys" { + run /bin/bash -c "dokku registry:set --global push-on-release" + assert_success + run /bin/bash -c "dokku registry:set --global server" + assert_success + run /bin/bash -c "dokku registry:set --global image-repo-template" + assert_success + run /bin/bash -c "dokku registry:set --global push-extra-tags" + assert_success + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-global-push-on-release\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-computed-push-on-release\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-global-server\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-computed-server\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-global-image-repo-template\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-computed-image-repo-template\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-global-push-extra-tags\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-computed-push-extra-tags\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:set --global push-on-release true" + assert_success + run /bin/bash -c "dokku registry:set --global server ghcr.io" + assert_success + run /bin/bash -c "dokku registry:set --global image-repo-template 'dokku/{{ APP }}'" + assert_success + run /bin/bash -c "dokku registry:set --global push-extra-tags latest,stable" + assert_success + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-global-push-on-release\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-computed-push-on-release\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-global-server\"'" + assert_success + assert_output "ghcr.io" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-computed-server\"'" + assert_success + assert_output "ghcr.io/" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-global-image-repo-template\"'" + assert_success + assert_output 'dokku/{{ APP }}' + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-computed-image-repo-template\"'" + assert_success + assert_output 'dokku/{{ APP }}' + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-global-push-extra-tags\"'" + assert_success + assert_output "latest,stable" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r '.\"registry-computed-push-extra-tags\"'" + assert_success + assert_output "latest,stable" + + run /bin/bash -c "dokku registry:set --global push-on-release" + assert_success + run /bin/bash -c "dokku registry:set --global server" + assert_success + run /bin/bash -c "dokku registry:set --global image-repo-template" + assert_success + run /bin/bash -c "dokku registry:set --global push-extra-tags" + assert_success +} + +@test "(registry:report) push-extra-tags raw vs computed vs global" { + run /bin/bash -c "dokku registry:set --global push-extra-tags" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-push-extra-tags\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-push-extra-tags\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-push-extra-tags\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku registry:set --global push-extra-tags global-tag" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-global-push-extra-tags\"'" + assert_success + assert_output "global-tag" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-push-extra-tags\"'" + assert_success + assert_output "global-tag" + + run /bin/bash -c "dokku registry:set $TEST_APP push-extra-tags release,stable" + assert_success + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-push-extra-tags\"'" + assert_success + assert_output "release,stable" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r '.\"registry-computed-push-extra-tags\"'" + assert_success + assert_output "release,stable" + + run /bin/bash -c "dokku registry:set $TEST_APP push-extra-tags" + assert_success + + run /bin/bash -c "dokku registry:set --global push-extra-tags" + assert_success +} + @test "(registry) registry:set server" { run /bin/bash -c "dokku registry:set --global server ghcr.io" echo "output: $output" @@ -294,3 +690,56 @@ teardown() { echo "status: $status" assert_success } + +@test "(registry) registry:set push-extra-tags" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + run /bin/bash -c "dokku registry:set $TEST_APP push-on-release true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:set $TEST_APP image-repo dokku/test-app" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:set $TEST_APP push-extra-tags foo" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dokku/test-app:foo" +} + +@test "(registry:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"image-repo\") and has(\"registry-image-repo\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"computed-image-repo\") and has(\"registry-computed-image-repo\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"push-on-release\") and has(\"registry-push-on-release\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"global-push-on-release\") and has(\"registry-global-push-on-release\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report $TEST_APP --format json | jq -r 'has(\"computed-server\") and has(\"registry-computed-server\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku registry:report --global --format json | jq -r 'has(\"global-image-repo-template\") and has(\"registry-global-image-repo-template\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/registry_2.bats b/tests/unit/registry_2.bats new file mode 100644 index 00000000000..1b10eacd9d9 --- /dev/null +++ b/tests/unit/registry_2.bats @@ -0,0 +1,100 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app + dokku checks:set $TEST_APP wait-to-retire 30 +} + +teardown() { + destroy_app + global_teardown +} + +setup_push_on_release_app() { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + run /bin/bash -c "dokku builder:set $TEST_APP selected herokuish" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP nginx" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:login $TEST_APP docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:set $TEST_APP image-repo $DOCKERHUB_USERNAME/$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:set $TEST_APP push-on-release true" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image rm $DOCKERHUB_USERNAME/$TEST_APP:1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image inspect $DOCKERHUB_USERNAME/$TEST_APP:1" + echo "output: $output" + echo "status: $status" + assert_failure +} + +@test "(registry) [recover] ps:restart pulls missing image" { + setup_push_on_release_app + + run /bin/bash -c "dokku ps:restart $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "not found locally, pulling from registry" + + run /bin/bash -c "docker image inspect $DOCKERHUB_USERNAME/$TEST_APP:1" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(registry) [recover] dokku run pulls missing image" { + setup_push_on_release_app + + run /bin/bash -c "dokku run $TEST_APP echo recovery-ok" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "not found locally, pulling from registry" + assert_output_contains "recovery-ok" +} + +@test "(registry) [recover] domains:add pulls missing image" { + setup_push_on_release_app + + run /bin/bash -c "dokku domains:add $TEST_APP recovery.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker image inspect $DOCKERHUB_USERNAME/$TEST_APP:1" + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/tests/unit/repo.bats b/tests/unit/repo.bats index abdc68213fe..02678d8133c 100644 --- a/tests/unit/repo.bats +++ b/tests/unit/repo.bats @@ -4,9 +4,11 @@ load test_helper setup() { global_setup + create_app } teardown() { + destroy_app global_teardown } @@ -24,7 +26,7 @@ teardown() { assert_output "$help_output" } -@test "(repo) repo:gc, repo:purge-cache" { +@test "(repo) repo:gc" { run deploy_app echo "output: $output" echo "status: $status" @@ -34,29 +36,26 @@ teardown() { echo "output: $output" echo "status: $status" assert_success +} - run /bin/bash -c "touch $DOKKU_ROOT/$TEST_APP/cache/derp" +@test "(repo) repo:purge-cache" { + run deploy_app echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "find $DOKKU_ROOT/$TEST_APP/cache -type f | wc -l" + run /bin/bash -c "docker volume ls -q --filter label=com.dokku.app-name=$TEST_APP | wc -l" echo "count: '$output'" echo "status: $status" - assert_not_output 0 + assert_output 1 run /bin/bash -c "dokku repo:purge-cache $TEST_APP" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "find $DOKKU_ROOT/$TEST_APP/cache -type f | wc -l" + run /bin/bash -c "docker volume ls -q --filter label=com.dokku.app-name=$TEST_APP | wc -l" echo "count: '$output'" echo "status: $status" assert_output 0 - - run destroy_app - echo "output: $output" - echo "status: $status" - assert_success } diff --git a/tests/unit/report.bats b/tests/unit/report.bats index 2c3d27f6b17..13f96c81058 100644 --- a/tests/unit/report.bats +++ b/tests/unit/report.bats @@ -4,17 +4,18 @@ load test_helper setup() { global_setup - deploy_app rm -f /tmp/fake-docker-bin } teardown() { rm -f /tmp/fake-docker-bin - destroy_app + destroy_app || true global_teardown } @test "(report) report" { + deploy_app + run /bin/bash -c "dokku report" echo "output: $output" echo "status: $status" @@ -25,7 +26,7 @@ teardown() { echo "output: $output" echo "status: $status" assert_output_contains "herokuish version:" - assert_output_contains "Deployed: false" "0" + assert_output_contains "Deployed: false" "0" assert_success run /bin/bash -c "dokku report fake-app-name" @@ -39,17 +40,26 @@ teardown() { assert_output_contains "App fake-app-name does not exist" assert_failure - dokku apps:create "${TEST_APP}-2" + run /bin/bash -c "dokku apps:create ${TEST_APP}-2" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku report ${TEST_APP}-2 2>&1" echo "output: $output" echo "status: $status" - assert_output_contains "Deployed: false" + assert_output_contains "Deployed: false" assert_success - dokku --force apps:destroy "${TEST_APP}-2" + run /bin/bash -c "dokku --force apps:destroy ${TEST_APP}-2" + echo "output: $output" + echo "status: $status" + assert_success } @test "(report) custom docker bin" { + deploy_app + export DOCKER_BIN="docker" run /bin/bash -c "dokku report" echo "output: $output" @@ -78,3 +88,112 @@ teardown() { unset DOCKER_BIN } + +@test "(report) exit 0 when no apps exist" { + run /bin/bash -c "dokku report" + echo "output: $output" + echo "status: $status" + assert_success + + plugins=( + app-json + apps + builder + builder-dockerfile + builder-herokuish + builder-lambda + builder-nixpacks + builder-railpack + builder-pack + buildpacks + caddy + certs + checks + cron + docker-options + domains + git + haproxy + logs + network + nginx + openresty + ports + proxy + ps + registry + resource + scheduler + scheduler-docker-local + scheduler-k3s + storage + traefik + ) + + for plugin in "${plugins[@]}"; do + run /bin/bash -c "dokku $plugin:report 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "You haven't deployed any applications yet" + done +} + +@test "(report) storage report when apps exist" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + plugins=( + app-json + apps + builder + builder-dockerfile + builder-herokuish + builder-lambda + builder-nixpacks + builder-railpack + builder-pack + buildpacks + caddy + certs + checks + cron + docker-options + domains + git + haproxy + logs + network + nginx + openresty + ports + proxy + ps + registry + resource + scheduler + scheduler-docker-local + scheduler-k3s + storage + traefik + ) + + for plugin in "${plugins[@]}"; do + run /bin/bash -c "dokku $plugin:report 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku $plugin:report $TEST_APP 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + done +} diff --git a/tests/unit/resource_1.bats b/tests/unit/resource_1.bats index 54919bb0200..c36031e89ad 100644 --- a/tests/unit/resource_1.bats +++ b/tests/unit/resource_1.bats @@ -32,20 +32,44 @@ teardown() { echo "status: $status" assert_output_contains "resource limits $TEST_APP information" - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku resource:limit --memory 512MB $TEST_APP" echo "output: $output" echo "status: $status" assert_success - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" echo "status: $status" assert_output "0" - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + + run /bin/bash -c "dokku resource:limit --memory 512 $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" echo "status: $status" @@ -56,8 +80,12 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" echo "status: $status" @@ -68,8 +96,12 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" echo "status: $status" @@ -80,8 +112,12 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" echo "status: $status" @@ -92,8 +128,12 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" echo "output: $output" echo "status: $status" diff --git a/tests/unit/resource_2.bats b/tests/unit/resource_2.bats index 2707bdd8a9c..5ef9bfde19a 100644 --- a/tests/unit/resource_2.bats +++ b/tests/unit/resource_2.bats @@ -18,20 +18,28 @@ teardown() { echo "status: $status" assert_output_contains "resource reservation $TEST_APP information" - deploy_app + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku resource:reserve --memory 512MB $TEST_APP" echo "output: $output" echo "status: $status" assert_success - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" echo "status: $status" assert_output "0" - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" echo "status: $status" @@ -47,8 +55,12 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" echo "status: $status" @@ -59,8 +71,12 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" echo "status: $status" @@ -71,8 +87,12 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" echo "status: $status" @@ -83,8 +103,12 @@ teardown() { echo "status: $status" assert_success - dokku ps:rebuild "$TEST_APP" - CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" echo "output: $output" echo "status: $status" diff --git a/tests/unit/resource_3.bats b/tests/unit/resource_3.bats new file mode 100644 index 00000000000..174d2084d54 --- /dev/null +++ b/tests/unit/resource_3.bats @@ -0,0 +1,131 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + destroy_app + global_teardown +} + +@test "(resource) resource:limit --process-type build (herokuish)" { + run /bin/bash -c "dokku resource:limit --memory 256m --cpu 1 --memory-swap 512m --nvidia-gpu 1 --process-type build $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku resource:report --resource-build.limit.memory $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output "256m" + + run_plugn_trigger docker-args-process-build "$TEST_APP" herokuish + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "--memory=256m" + assert_output_contains "--cpus=1" + assert_output_contains "--memory-swap=512m" + assert_output_contains "--gpus=1" +} + +@test "(resource) resource:limit --process-type build (dockerfile filters cpu and gpu)" { + run /bin/bash -c "dokku resource:limit --memory 512m --cpu 1 --nvidia-gpu 1 --memory-swap 1g --process-type build $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugn_trigger docker-args-process-build "$TEST_APP" dockerfile + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "--memory=512m" + assert_output_contains "--memory-swap=1g" + assert_output_not_contains "--cpus=" + assert_output_not_contains "--gpus=" +} + +@test "(resource) resource:limit --process-type build (pack/lambda emit nothing)" { + run /bin/bash -c "dokku resource:limit --memory 512m --process-type build $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugn_trigger docker-args-process-build "$TEST_APP" pack + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "--memory" + + run_plugn_trigger docker-args-process-build "$TEST_APP" lambda + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "--memory" +} + +@test "(resource) resource:limit defaults do not leak into build trigger" { + run /bin/bash -c "dokku resource:limit --memory 128m $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugn_trigger docker-args-process-build "$TEST_APP" herokuish + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "--memory" +} + +@test "(resource) resource:limit reservations do not apply at build time" { + run /bin/bash -c "dokku resource:reserve --memory 128m --process-type build $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugn_trigger docker-args-process-build "$TEST_APP" herokuish + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "--memory-reservation" + assert_output_not_contains "--memory" +} + +@test "(resource) DOKKU_OMIT_RESOURCE_ARGS suppresses build trigger" { + run /bin/bash -c "dokku resource:limit --memory 256m --process-type build $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run_plugn_trigger DOKKU_OMIT_RESOURCE_ARGS=1 docker-args-process-build "$TEST_APP" herokuish + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "--memory" +} + +@test "(resource) resource:limit-clear --process-type build" { + run /bin/bash -c "dokku resource:limit --memory 256m --process-type build $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku resource:report --resource-build.limit.memory $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output "256m" + + run /bin/bash -c "dokku resource:limit-clear --process-type build $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku resource:report --resource-build.limit.memory $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure +} diff --git a/tests/unit/run_1.bats b/tests/unit/run_1.bats index d157ef4f0d9..91fda6ce9da 100644 --- a/tests/unit/run_1.bats +++ b/tests/unit/run_1.bats @@ -24,8 +24,12 @@ teardown() { } @test "(run) run (with --options)" { - deploy_app - run /bin/bash -c "dokku --force --quiet run $TEST_APP python -V" + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --force --quiet run $TEST_APP python3 -V" echo "output: $output" echo "status: $status" assert_success @@ -42,7 +46,7 @@ teardown() { echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP add_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt echo "output: $output" echo "status: $status" assert_success @@ -64,12 +68,17 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "dokku config:set --no-restart $TEST_APP DOKKU_CNB_EXPERIMENTAL=1 SECRET_KEY=fjdkslafjdk" + run /bin/bash -c "dokku config:set --no-restart $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" echo "output: $output" echo "status: $status" assert_success - run deploy_app python dokku@dokku.me:$TEST_APP add_requirements_txt + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt_cnb echo "output: $output" echo "status: $status" assert_success @@ -89,3 +98,45 @@ teardown() { echo "status: $status" assert_success } + +@test "(run) run:retire" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run:detached --ttl-seconds=1 $TEST_APP sleep 300" + echo "output: $output" + echo "status: $status" + assert_success + container_id="$output" + + # check the labels on the container to ensure the active deadline seconds is set + run /bin/bash -c "docker container inspect $container_id --format '{{ index .Config.Labels \"com.dokku.active-deadline-seconds\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + run /bin/bash -c "dokku run:list --quiet $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "$container_id" + + run /bin/bash -c "sleep 2" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run:retire" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run:list --quiet $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "$container_id" +} diff --git a/tests/unit/run_2.bats b/tests/unit/run_2.bats index 0be97ab6741..b80957028cb 100644 --- a/tests/unit/run_2.bats +++ b/tests/unit/run_2.bats @@ -12,8 +12,11 @@ teardown() { global_teardown } -@test "(core) run" { - deploy_app +@test "(run) run" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku run $TEST_APP echo $TEST_APP" echo "output: $output" @@ -26,8 +29,11 @@ teardown() { assert_failure } -@test "(core) run:detached" { - deploy_app +@test "(run) run:detached" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success RANDOM_RUN_CID="$(dokku --label=com.dokku.test-label=value run:detached $TEST_APP sleep 300)" run /bin/bash -c "docker inspect -f '{{ .State.Status }}' $RANDOM_RUN_CID" @@ -49,27 +55,39 @@ teardown() { echo "output: $output" echo "status: $status" assert_success - sleep 5 # wait for dokku cleanup to happen in the background + sleep 5 # wait for dokku cleanup to happen in the background } -@test "(core) run (with tty)" { - deploy_app +@test "(run) run (with tty)" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku run $TEST_APP ls /app/null" echo "output: $output" echo "status: $status" assert_success } -@test "(core) run (without tty)" { - deploy_app +@test "(run) run (without tty)" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c ": |dokku run $TEST_APP ls /app/null" echo "output: $output" echo "status: $status" assert_success } -@test "(core) run command from Procfile" { - deploy_app +@test "(run) run command from Procfile" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku run $TEST_APP custom 'hi dokku' | tail -n 1" echo "output: $output" echo "status: $status" @@ -77,3 +95,104 @@ teardown() { assert_success assert_output 'hi dokku' } + +@test "(run) list" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run:list $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run:detached $TEST_APP sleep 300" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run:list $TEST_APP | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + run /bin/bash -c "dokku run:list $TEST_APP --format json | jq '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + run /bin/bash -c "dokku run:list --format json $TEST_APP | jq '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + run /bin/bash -c "dokku run:detached $TEST_APP sleep 300" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run:list $TEST_APP | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" + + run /bin/bash -c "dokku run:list $TEST_APP --format json | jq '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" + + run /bin/bash -c "dokku run:list --format json $TEST_APP | jq '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" +} + +@test "(run) docker-options and -e flags are not eval-injected" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP run '--label=com.dokku.test=safe'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run --ttl-seconds=60 -e FOO=bar $TEST_APP env | grep -E '^FOO=bar'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku run -e 'X=\$(id)' $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + [[ "$output" == *'X=$(id)'* ]] || flunk "expected literal X=\$(id) in container env" + [[ "$output" != *"uid="* ]] || flunk "id command output leaked - env value was expanded" +} + +@test "(run) --ttl-seconds rejects non-numeric input; docker-options stores it verbatim" { + run /bin/bash -c "dokku run --ttl-seconds '\$(id)' $TEST_APP echo hi" + echo "output: $output" + echo "status: $status" + assert_failure + [[ "$output" != *"uid="* ]] || flunk "id command output leaked - ttl value was expanded" + + run /bin/bash -c "dokku docker-options:add $TEST_APP run '--label=x=\$(id)'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-run" + echo "output: $output" + echo "status: $status" + [[ "$output" == *'x=$(id)'* ]] || flunk "expected literal x=\$(id) stored in docker options" + [[ "$output" != *"uid="* ]] || flunk "id command output leaked - docker option was expanded" +} diff --git a/tests/unit/scheduler-docker-local.bats b/tests/unit/scheduler-docker-local.bats index a922b042815..29c76196c15 100644 --- a/tests/unit/scheduler-docker-local.bats +++ b/tests/unit/scheduler-docker-local.bats @@ -10,6 +10,181 @@ teardown() { global_teardown } +@test "(scheduler-docker-local:report) --global raw and computed keys" { + run /bin/bash -c "dokku scheduler-docker-local:report --global --scheduler-docker-local-global-init-process" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report --global --scheduler-docker-local-computed-init-process" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-docker-local:report --global --scheduler-docker-local-global-parallel-schedule-count" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report --global --scheduler-docker-local-computed-parallel-schedule-count" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" +} + +@test "(scheduler-docker-local:report) info-flag works before deploy" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --scheduler-docker-local-parallel-schedule-count" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --scheduler-docker-local-computed-parallel-schedule-count" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --scheduler-docker-local-global-parallel-schedule-count" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --scheduler-docker-local-init-process" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --scheduler-docker-local-computed-init-process" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --scheduler-docker-local-global-init-process" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --scheduler-docker-local-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" + + destroy_app +} + +@test "(scheduler-docker-local:report) --format json" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"init-process\"'" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"computed-init-process\"'" + echo "output: $output" + echo "status: $status" + assert_output "true" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"global-init-process\"'" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"parallel-schedule-count\"'" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"computed-parallel-schedule-count\"'" + echo "output: $output" + echo "status: $status" + assert_output "1" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"global-parallel-schedule-count\"'" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:set $TEST_APP init-process false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"init-process\"'" + echo "output: $output" + echo "status: $status" + assert_output "false" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"computed-init-process\"'" + echo "output: $output" + echo "status: $status" + assert_output "false" + + destroy_app +} + +@test "(scheduler-docker-local:set) --global" { + run /bin/bash -c "dokku scheduler-docker-local:set --global parallel-schedule-count 4" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-docker-local:report --global --format json | jq -r '.\"global-parallel-schedule-count\"'" + echo "output: $output" + echo "status: $status" + assert_output "4" + + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"parallel-schedule-count\"'" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"computed-parallel-schedule-count\"'" + echo "output: $output" + echo "status: $status" + assert_output "4" + + run /bin/bash -c "dokku scheduler-docker-local:report $TEST_APP --format json | jq -r '.\"global-parallel-schedule-count\"'" + echo "output: $output" + echo "status: $status" + assert_output "4" + + run /bin/bash -c "dokku scheduler-docker-local:set --global parallel-schedule-count" + echo "output: $output" + echo "status: $status" + assert_success + + destroy_app +} + @test "(scheduler-docker-local) scheduler-docker-local:help" { run /bin/bash -c "dokku scheduler-docker-local" echo "output: $output" @@ -30,3 +205,281 @@ teardown() { echo "status: $status" assert_success } + +@test "(scheduler-docker-local) complex labels" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + # the run command is equivalent to the following line, except with backslashes due to the enclosing doublequotes + # dokku docker-options:add test deploy '--label "some.key=Host(\`$TEST_APP.$DOKKU_DOMAIN\`)"' + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '--label \"some.key=Host(\\\`$TEST_APP.$DOKKU_DOMAIN\\\`)\"'" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect --format '{{ index .Config.Labels \"some.key\"}}' $TEST_APP.web.1" + echo "output: $output" + echo "status: $status" + assert_output "Host(\`$TEST_APP.$DOKKU_DOMAIN\`)" + assert_success +} + +@test "(scheduler-docker-local) no-web" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:set $TEST_APP procfile-path worker.Procfile" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Skipping web as it is missing from the current Procfile" +} + +@test "(scheduler-docker-local) init-process" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect --format '{{.HostConfig.Init}}' $TEST_APP.web.1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "docker exec $TEST_APP.web.1 ps auxf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "docker-init" + + run /bin/bash -c "dokku scheduler-docker-local:set $TEST_APP init-process false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect --format '{{.HostConfig.Init}}' $TEST_APP.web.1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "docker exec $TEST_APP.web.1 ps auxf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "docker-init" 0 +} + +@test "(scheduler-docker-local) publish ports" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Deploys may fail when publishing ports and scaling to multiple containers" 0 + assert_output_contains "Deploys may fail when publishing ports and enabling zero downtime" 0 + + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '--publish 5000:5000'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale --skip-deploy $TEST_APP web=2" + echo "output: $output" + echo "status: $status" + assert_success + + # the expected output will be seen twice due to how parallel re-outputs stderr in its own output... + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Deploys may fail when publishing ports and scaling to multiple containers" 2 + assert_output_contains "Deploys may fail when publishing ports and enabling zero downtime" 0 + + run /bin/bash -c "dokku ps:scale --skip-deploy $TEST_APP web=1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Deploys may fail when publishing ports and scaling to multiple containers" 0 + assert_output_contains "Deploys may fail when publishing ports and enabling zero downtime" 2 + + run /bin/bash -c "dokku checks:disable $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Deploys may fail when publishing ports and scaling to multiple containers" 0 + assert_output_contains "Deploys may fail when publishing ports and enabling zero downtime" 0 +} + +@test "(scheduler-docker-local) sends SIGTERM immediately on deploy" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:set $TEST_APP wait-to-retire 600" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + local old_cid + old_cid="$(docker ps --filter label=com.dokku.app-name=$TEST_APP --filter label=com.dokku.process-type=web --format '{{.ID}}')" + [[ -n "$old_cid" ]] + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + sleep 5 + + run /bin/bash -c "docker inspect --format '{{.State.Status}}' $old_cid" + echo "output: $output" + echo "status: $status" + assert_output "exited" + + run /bin/bash -c "docker ps --filter label=com.dokku.app-name=$TEST_APP --filter label=com.dokku.process-type=web --format '{{.Names}}'" + echo "output: $output" + echo "status: $status" + assert_output "$TEST_APP.web.1" +} + +@test "(scheduler-docker-local) scale down retires orphaned containers" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:set $TEST_APP wait-to-retire 1" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=2" + echo "output: $output" + echo "status: $status" + assert_success + + sleep 2 + + run /bin/bash -c "dokku ps:retire" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker ps --filter label=com.dokku.app-name=$TEST_APP --filter label=com.dokku.process-type=web --format '{{.Names}}' | wc -l" + echo "output: $output" + echo "status: $status" + assert_output "2" + + run /bin/bash -c "dokku ps:scale $TEST_APP web=1" + echo "output: $output" + echo "status: $status" + assert_success + + sleep 2 + + run /bin/bash -c "dokku ps:retire" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker ps --filter label=com.dokku.app-name=$TEST_APP --filter label=com.dokku.process-type=web --format '{{.Names}}'" + echo "output: $output" + echo "status: $status" + assert_output "$TEST_APP.web.1" +} + +@test "(scheduler-docker-local) ps:rebuild with image-based deploy keeps running image" { + run create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku checks:set $TEST_APP wait-to-retire 1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP cockpithq/cockpit:core-latest" + echo "output: $output" + echo "status: $status" + assert_success + + local running_image + running_image="$(docker container inspect "$TEST_APP.web.1" --format '{{.Image}}' | cut -d: -f2)" + [[ -n "$running_image" ]] + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + sleep 3 + + run /bin/bash -c "dokku ps:retire" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "has running containers, skipping rm" 0 + + run /bin/bash -c "grep -F \"$running_image\" /var/lib/dokku/data/scheduler-docker-local/dead-images 2>/dev/null || true" + echo "output: $output" + echo "status: $status" + assert_output "" + + run /bin/bash -c "docker container inspect $TEST_APP.web.1 --format '{{.State.Status}}'" + echo "output: $output" + echo "status: $status" + assert_output "running" +} diff --git a/tests/unit/scheduler-k3s-1.bats b/tests/unit/scheduler-k3s-1.bats new file mode 100644 index 00000000000..b89be6d0ea9 --- /dev/null +++ b/tests/unit/scheduler-k3s-1.bats @@ -0,0 +1,64 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) install traefik with taint" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + TAINT_SCHEDULING=true install_k3s +} + +@test "(scheduler-k3s) install nginx with taint" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx TAINT_SCHEDULING=true install_k3s +} + +@test "(scheduler-k3s) deploy dockerfile exposed port" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:from-image $TEST_APP metabase/metabase:latest" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 30" + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response_contains "http" "$TEST_APP.dokku.me" "80" "" "Metabase" +} diff --git a/tests/unit/scheduler-k3s-2.bats b/tests/unit/scheduler-k3s-2.bats new file mode 100644 index 00000000000..978bfa53140 --- /dev/null +++ b/tests/unit/scheduler-k3s-2.bats @@ -0,0 +1,285 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) deploy traefik [resource] [autoscaling1]" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=traefik install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP memory --metadata some-key=1234567890 --metadata some-value=asdfghjkl" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git" + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" + + # include autoscaling tests + run /bin/bash -c "kubectl get scaledobjects.keda.sh $TEST_APP-web -o=jsonpath='{.spec.triggers[0].authenticationRef.name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP-memory" + + run /bin/bash -c "kubectl get triggerauthentications.keda.sh $TEST_APP-memory -o=jsonpath='{.spec.secretTargetRef[0].key}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "some-key" + + run /bin/bash -c "kubectl get triggerauthentications.keda.sh $TEST_APP-memory -o=jsonpath='{.spec.secretTargetRef[0].name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "kta-$TEST_APP-memory" + + run /bin/bash -c "kubectl get triggerauthentications.keda.sh $TEST_APP-memory -o=jsonpath='{.spec.secretTargetRef[0].parameter}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "some-key" + + run /bin/bash -c "kubectl get secret kta-$TEST_APP-memory -o=jsonpath='{.data.some-key}' | base64 --decode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1234567890" + + run /bin/bash -c "kubectl get secret kta-$TEST_APP-memory -o=jsonpath='{.data.some-value}' | base64 --decode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "asdfghjkl" + + # include resource tests + run /bin/bash -c "kubectl get pods -o=jsonpath='{.items[*].spec.containers[*].resources.requests.cpu}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "100m" + + run /bin/bash -c "kubectl get pods -o=jsonpath='{.items[*].spec.containers[*].resources.requests.memory}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "128Mi" + + run /bin/bash -c "kubectl get pods -o=jsonpath='{.items[*].spec.containers[*].resources.limits.cpu}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get pods -o=jsonpath='{.items[*].spec.containers[*].resources.limits.memory}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku resource:reserve $TEST_APP --memory 300 --cpu 0m --process-type web" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku resource:limit $TEST_APP --memory 512 --process-type web" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" + + run /bin/bash -c "kubectl get pods -o=jsonpath='{.items[*].spec.containers[*].resources.requests.cpu}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get pods -o=jsonpath='{.items[*].spec.containers[*].resources.requests.memory}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "300Mi" + + run /bin/bash -c "kubectl get pods -o=jsonpath='{.items[*].spec.containers[*].resources.limits.cpu}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get pods -o=jsonpath='{.items[*].spec.containers[*].resources.limits.memory}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "512Mi" + + # include run tests + run /bin/bash -c "dokku run $TEST_APP ls -lah" + echo "output: $output" + echo "status: $status" + assert_success + + # include enter tests + run /bin/bash -c "dokku enter $TEST_APP web ls -lah" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(scheduler-k3s) deploy nginx" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:set $TEST_APP shm-size 64Mi" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git" + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.volumes[0].emptyDir.sizeLimit'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "64Mi" + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.volumes[0].emptyDir.medium'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "Memory" +} + +@test "(scheduler-k3s) deploy annotations" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type secret test.dokku.com/resource-type secret" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type pod test.dokku.com/resource-type pod" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type job test.dokku.com/resource-type job" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment test.dokku.com/resource-type deployment" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --process-type web --resource-type deployment test.dokku.com/resource-type deployment-web" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku git:sync --build $TEST_APP https://github.com/dokku/smoke-test-app.git" + echo "output: $output" + echo "status: $status" + assert_success + + assert_http_localhost_response "http" "$TEST_APP.dokku.me" "80" "" "python/http.server" + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.metadata.annotations.\"test.dokku.com/resource-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "deployment-web" + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.metadata.annotations.\"test.dokku.com/resource-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "pod" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.annotations.\"test.dokku.com/resource-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "null" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].spec.jobTemplate.metadata.annotations.\"test.dokku.com/resource-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "job" + + run /bin/bash -c "kubectl get secret -o json | jq -r '.items[0].metadata.annotations.\"test.dokku.com/resource-type\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secret" +} diff --git a/tests/unit/scheduler-k3s-3.bats b/tests/unit/scheduler-k3s-3.bats new file mode 100644 index 00000000000..1f6be842800 --- /dev/null +++ b/tests/unit/scheduler-k3s-3.bats @@ -0,0 +1,160 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) security context" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.containers[0].securityContext.capabilities.add[0]'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "null" + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.containers[0].securityContext.privileged'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "null" + + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy --cap-add=NET_ADMIN" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy --privileged" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:restart $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.containers[0].securityContext.capabilities.add[0]'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "NET_ADMIN" + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.containers[0].securityContext.privileged'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" +} + +@test "(scheduler-k3s) kustomize" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=1 worker=2" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP inject_kustomization_yaml + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.replicas'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "3" + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o json | jq -r '.spec.replicas'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" +} + +@test "(scheduler-k3s) deploy kustomize with vector sink" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + encoded="$(echo '{{ print "{{ pod }}" }}' | base64)" + run /bin/bash -c "echo $encoded" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "e3sgcHJpbnQgInt7IHBvZCB9fSIgfX0K" + + run /bin/bash -c "dokku logs:set --global vector-sink 'http://?process=base64enc%3A${encoded}'" + echo "output: $output" + echo "status: $status" + assert_success + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "kubectl get cm -n vector vector -o yaml" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "process: '{{ pod }}'" +} + +inject_kustomization_yaml() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mkdir -p "$APP_REPO_DIR/config/kustomize" + touch "$APP_REPO_DIR/config/kustomize/kustomization.yaml" + cat <"$APP_REPO_DIR/config/kustomize/kustomization.yaml" +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- rendered.yaml +patches: +- patch: |- + - op: replace + path: /spec/replicas + value: 3 + target: + group: apps + version: v1 + kind: Deployment + name: $APP-web +EOF +} diff --git a/tests/unit/scheduler-k3s-4.bats b/tests/unit/scheduler-k3s-4.bats new file mode 100644 index 00000000000..740f56901c9 --- /dev/null +++ b/tests/unit/scheduler-k3s-4.bats @@ -0,0 +1,187 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) app.json defined service" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=1 worker=1" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP inject_app_json + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get services $TEST_APP-web -o json | jq -r '.spec.ports[0].port'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "80" + + run /bin/bash -c "kubectl get services $TEST_APP-web -o json | jq -r '.spec.ports[0].targetPort'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5000" +} + +@test "(scheduler-k3s) deploy autoscaling cpu-only trigger [keda-fallback]" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP inject_cpu_autoscaling_app_json + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get scaledobjects.keda.sh $TEST_APP-web -o=jsonpath='{.spec.fallback}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get scaledobjects.keda.sh $TEST_APP-web -o=jsonpath='{.spec.triggers[0].type}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "cpu" +} + +@test "(scheduler-k3s) deploy autoscaling external trigger [keda-fallback]" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP inject_prometheus_autoscaling_app_json + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get scaledobjects.keda.sh $TEST_APP-web -o=jsonpath='{.spec.fallback.failureThreshold}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "3" + + run /bin/bash -c "kubectl get scaledobjects.keda.sh $TEST_APP-web -o=jsonpath='{.spec.fallback.replicas}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" +} + +inject_app_json() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + cat <"$APP_REPO_DIR/app.json" +{ + "formation": { + "worker": { + "service": { + "exposed": true + } + } + } +} +EOF +} + +inject_cpu_autoscaling_app_json() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + cat <"$APP_REPO_DIR/app.json" +{ + "formation": { + "web": { + "autoscaling": { + "min_quantity": 1, + "max_quantity": 3, + "triggers": [ + { + "name": "cpu-trigger", + "type": "cpu", + "metadata": { + "value": "80" + } + } + ] + } + } + } +} +EOF +} + +inject_prometheus_autoscaling_app_json() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + cat <"$APP_REPO_DIR/app.json" +{ + "formation": { + "web": { + "autoscaling": { + "min_quantity": 1, + "max_quantity": 3, + "triggers": [ + { + "name": "prom-trigger", + "type": "prometheus", + "metadata": { + "serverAddress": "http://prometheus.example.com:9090", + "metricName": "http_requests_total", + "threshold": "100", + "query": "sum(rate(http_requests_total{deployment=\"[[ .DEPLOYMENT_NAME ]]\"}[2m]))" + } + } + ] + } + } + } +} +EOF +} diff --git a/tests/unit/scheduler-k3s-annotations.bats b/tests/unit/scheduler-k3s-annotations.bats new file mode 100644 index 00000000000..8ff04b1a389 --- /dev/null +++ b/tests/unit/scheduler-k3s-annotations.bats @@ -0,0 +1,115 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku apps:create $TEST_APP >/dev/null 2>/dev/null || true +} + +teardown() { + dokku --force apps:destroy $TEST_APP >/dev/null 2>/dev/null || true + dokku --force apps:destroy ${TEST_APP}-2 >/dev/null 2>/dev/null || true + for resource in deployment service pod cronjob job secret ingress serviceaccount; do + dokku scheduler-k3s:annotations:set --global --resource-type "$resource" foo "" >/dev/null 2>/dev/null || true + dokku scheduler-k3s:annotations:set --global --resource-type "$resource" bar "" >/dev/null 2>/dev/null || true + done + global_teardown +} + +@test "(scheduler-k3s:annotations:report) lists annotations after set, clears them after unset" { + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo bar" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format json | jq -r '.\"global.deployment.foo\"'" + assert_success + assert_output "bar" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP" + assert_success + assert_output_contains "$TEST_APP annotations information" + assert_output_contains "Annotation (global/deployment) foo:" + assert_output_contains "bar" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --scheduler-k3s-annotations.global.deployment.foo" + assert_success + assert_output "bar" + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:annotations:report) honours --process-type and --resource-type filters" { + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo bar" + assert_success + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --process-type web --resource-type deployment baz qux" + assert_success + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type service alpha beta" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --resource-type deployment --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "global.deployment.foo,web.deployment.baz" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --process-type web --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "web.deployment.baz" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --process-type web --resource-type deployment --format json | jq -r '.\"web.deployment.baz\"'" + assert_success + assert_output "qux" +} + +@test "(scheduler-k3s:annotations:report) supports --global scope and no-arg multi-app loop" { + run /bin/bash -c "dokku apps:create ${TEST_APP}-2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:set --global --resource-type deployment foo global-val" + assert_success + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo app1-val" + assert_success + run /bin/bash -c "dokku scheduler-k3s:annotations:set ${TEST_APP}-2 --resource-type deployment bar app2-val" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report --global --format json | jq -r '.\"global.deployment.foo\"'" + assert_success + assert_output "global-val" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report" + assert_success + assert_output_contains "$TEST_APP annotations information" + assert_output_contains "${TEST_APP}-2 annotations information" + assert_output_contains "app1-val" + assert_output_contains "app2-val" +} + +@test "(scheduler-k3s:annotations:set) preserves multi-line values and / in keys" { + local value=$'line one\nline two\nline three' + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment prometheus.io/scrape \"$value\"" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format json | jq -r '.\"global.deployment.prometheus.io/scrape\"'" + assert_success + assert_output "$value" +} + +@test "(scheduler-k3s:annotations:report) rejects invalid format and info-flag combinations" { + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format yaml" + assert_failure + assert_output_contains "Invalid format" + + run /bin/bash -c "dokku scheduler-k3s:annotations:set $TEST_APP --resource-type deployment foo bar" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --format json --scheduler-k3s-annotations.global.deployment.foo" + assert_failure + assert_output_contains "--format flag cannot be specified when specifying an info flag" + + run /bin/bash -c "dokku scheduler-k3s:annotations:report $TEST_APP --scheduler-k3s-annotations.global.deployment.missing" + assert_failure + assert_output_contains "Invalid flag passed" +} diff --git a/tests/unit/scheduler-k3s-autoscaling-auth.bats b/tests/unit/scheduler-k3s-autoscaling-auth.bats new file mode 100644 index 00000000000..3b342896236 --- /dev/null +++ b/tests/unit/scheduler-k3s-autoscaling-auth.bats @@ -0,0 +1,151 @@ +#!/usr/bin/env bats + +load test_helper + +scheduler_k3s_seed_trigger_auth() { + local scope="$1" + local trigger="$2" + local key="$3" + local value="$4" + local dir="/var/lib/dokku/config/scheduler-k3s/$scope" + sudo mkdir -p "$dir" + echo -n "$value" | sudo tee "$dir/trigger-auth.${trigger}.${key}" >/dev/null +} + +scheduler_k3s_clear_trigger_auth() { + local scope="$1" + local trigger="$2" + sudo rm -f "/var/lib/dokku/config/scheduler-k3s/$scope/trigger-auth.${trigger}."* 2>/dev/null || true +} + +setup() { + global_setup + dokku apps:create $TEST_APP >/dev/null 2>/dev/null || true + dokku apps:create ${TEST_APP}-2 >/dev/null 2>/dev/null || true +} + +teardown() { + dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog >/dev/null 2>/dev/null || true + dokku scheduler-k3s:autoscaling-auth:set $TEST_APP memory >/dev/null 2>/dev/null || true + dokku scheduler-k3s:autoscaling-auth:set ${TEST_APP}-2 datadog >/dev/null 2>/dev/null || true + scheduler_k3s_clear_trigger_auth "--global" "datadog" + dokku --force apps:destroy $TEST_APP >/dev/null 2>/dev/null || true + dokku --force apps:destroy ${TEST_APP}-2 >/dev/null 2>/dev/null || true + global_teardown +} + +@test "(scheduler-k3s:autoscaling-auth:report) no-arg loops all apps" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=secret-1" + assert_success + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set ${TEST_APP}-2 datadog --metadata apiKey=secret-2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report" + assert_success + assert_output_contains "$TEST_APP autoscaling-auth information" + assert_output_contains "${TEST_APP}-2 autoscaling-auth information" + assert_output_contains "Datadog:" 2 +} + +@test "(scheduler-k3s:autoscaling-auth:report) lists metadata in json and clears after unset" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=secret-1 --metadata appKey=secret-2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json | jq -r '.\"datadog.apiKey\"'" + assert_success + assert_output "secret-1" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json | jq -r '.\"datadog.appKey\"'" + assert_success + assert_output "secret-2" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --scheduler-k3s-autoscaling-auth.datadog.apiKey" + assert_success + assert_output "*******" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP" + assert_success + assert_output_contains "Datadog:" + assert_output_not_contains "Datadog apiKey:" + assert_output_not_contains "secret-1" + assert_output_not_contains "secret-2" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --include-metadata" + assert_success + assert_output_contains "Datadog apiKey:" + assert_output_contains "secret-1" + assert_output_contains "secret-2" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:autoscaling-auth:report) stdout includes metadata values with --include-metadata" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=super-secret-value" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP" + assert_success + assert_output_contains "Datadog:" + assert_output_contains "configured" + assert_output_not_contains "Datadog apiKey:" + assert_output_not_contains "super-secret-value" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --include-metadata" + assert_success + assert_output_contains "Datadog apiKey:" + assert_output_contains "super-secret-value" +} + +@test "(scheduler-k3s:autoscaling-auth:report) reports multiple triggers in json" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=secret-1" + assert_success + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP memory --metadata some-key=some-value" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "datadog.apiKey,memory.some-key" +} + +@test "(scheduler-k3s:autoscaling-auth:report) supports --global scope" { + scheduler_k3s_seed_trigger_auth "--global" "datadog" "apiKey" "global-secret" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report --global --format json | jq -r '.\"datadog.apiKey\"'" + assert_success + assert_output "global-secret" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report --global --include-metadata" + assert_success + assert_output_contains "--global autoscaling-auth information" + assert_output_contains "Datadog:" + assert_output_contains "Datadog apiKey:" + assert_output_contains "global-secret" +} + +@test "(scheduler-k3s:autoscaling-auth:report) returns empty json for unconfigured app" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:autoscaling-auth:report) rejects invalid format and info-flag combinations" { + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format yaml" + assert_failure + assert_output_contains "Invalid format" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:set $TEST_APP datadog --metadata apiKey=secret-1" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --format json --scheduler-k3s-autoscaling-auth.datadog.apiKey" + assert_failure + assert_output_contains "--format flag cannot be specified when specifying an info flag" + + run /bin/bash -c "dokku scheduler-k3s:autoscaling-auth:report $TEST_APP --scheduler-k3s-autoscaling-auth.datadog.missing" + assert_failure + assert_output_contains "Invalid flag passed" +} diff --git a/tests/unit/scheduler-k3s-certs-2.bats b/tests/unit/scheduler-k3s-certs-2.bats new file mode 100644 index 00000000000..c2ecdd70357 --- /dev/null +++ b/tests/unit/scheduler-k3s-certs-2.bats @@ -0,0 +1,71 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup_local_tls() { + TLS=$BATS_TMPDIR/tls + mkdir -p $TLS + tar xf $BATS_TEST_DIRNAME/server_ssl.tar -C $TLS + sudo chown -R dokku:dokku $TLS +} + +teardown_local_tls() { + TLS=$BATS_TMPDIR/tls + rm -R $TLS +} + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" + setup_local_tls +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true + teardown_local_tls +} + +@test "(scheduler-k3s:certs) app destruction deletes k8s TLS secret" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler:set $TEST_APP selected k3s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Installing TLS certificate for $TEST_APP" + + run /bin/bash -c "kubectl get secret tls-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku apps:destroy $TEST_APP --force" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret tls-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_failure +} diff --git a/tests/unit/scheduler-k3s-certs-deploy-2.bats b/tests/unit/scheduler-k3s-certs-deploy-2.bats new file mode 100644 index 00000000000..fc7a5b8c5d0 --- /dev/null +++ b/tests/unit/scheduler-k3s-certs-deploy-2.bats @@ -0,0 +1,74 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s:certs) app-level letsencrypt email renders a per-app namespaced Issuer" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:set $TEST_APP letsencrypt-server staging" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:set $TEST_APP letsencrypt-email-stag app@dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 30" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get certificate ${TEST_APP}-web -n default -o jsonpath='{.spec.issuerRef.kind}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "Issuer" + + run /bin/bash -c "kubectl get certificate ${TEST_APP}-web -n default -o jsonpath='{.spec.issuerRef.name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "${TEST_APP}-letsencrypt-stag" + + run /bin/bash -c "kubectl get issuer ${TEST_APP}-letsencrypt-stag -n default -o jsonpath='{.spec.acme.email}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "app@dokku.me" +} diff --git a/tests/unit/scheduler-k3s-certs-deploy.bats b/tests/unit/scheduler-k3s-certs-deploy.bats new file mode 100644 index 00000000000..e87db7b6d56 --- /dev/null +++ b/tests/unit/scheduler-k3s-certs-deploy.bats @@ -0,0 +1,143 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup_local_tls() { + TLS=$BATS_TMPDIR/tls + mkdir -p $TLS + tar xf $BATS_TEST_DIRNAME/server_ssl.tar -C $TLS + sudo chown -R dokku:dokku $TLS +} + +teardown_local_tls() { + TLS=$BATS_TMPDIR/tls + rm -R $TLS +} + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" + setup_local_tls +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true + teardown_local_tls +} + +@test "(scheduler-k3s:certs) deploy uses imported certificate when present" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 30" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get ingress ${TEST_APP}-web-${TEST_APP}-dokku-me -n default -o jsonpath='{.spec.tls[0].secretName}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "tls-$TEST_APP" +} + +@test "(scheduler-k3s:certs) deploy falls back to letsencrypt when no imported cert" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku scheduler-k3s:set --global letsencrypt-email-stag test@dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:set --global letsencrypt-server staging" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 30" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get ingress ${TEST_APP}-web-${TEST_APP}-dokku-me -n default -o jsonpath='{.spec.tls[0].secretName}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "tls-${TEST_APP}-web" +} + +@test "(scheduler-k3s:certs) no TLS secret created for non-k3s scheduler app" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler:set $TEST_APP selected docker-local" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret tls-$TEST_APP -n default 2>&1" + echo "output: $output" + echo "status: $status" + assert_failure +} diff --git a/tests/unit/scheduler-k3s-certs-redirect.bats b/tests/unit/scheduler-k3s-certs-redirect.bats new file mode 100644 index 00000000000..3f5951196a1 --- /dev/null +++ b/tests/unit/scheduler-k3s-certs-redirect.bats @@ -0,0 +1,86 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup_local_tls() { + TLS=$BATS_TMPDIR/tls + mkdir -p $TLS + tar xf $BATS_TEST_DIRNAME/server_ssl.tar -C $TLS + sudo chown -R dokku:dokku $TLS +} + +teardown_local_tls() { + TLS=$BATS_TMPDIR/tls + rm -R $TLS +} + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" + setup_local_tls +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true + teardown_local_tls +} + +@test "(scheduler-k3s) [ingress] traefik redirects http to https when tls is enabled" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=traefik install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 30" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get ingressroutes.traefik.io ${TEST_APP}-web-http-80-5000 -n default -o jsonpath='{.spec.routes[0].middlewares[1].name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "${TEST_APP}-web-redirect-to-https" + + run /bin/bash -c "kubectl get ingressroutes.traefik.io ${TEST_APP}-web-http-80-5000 -n default -o jsonpath='{.spec.tls.secretName}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get ingressroutes.traefik.io ${TEST_APP}-web-http-80-5000-websecure -n default -o jsonpath='{.spec.tls.secretName}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "tls-${TEST_APP}" + + assert_http_redirect "http://$TEST_APP.dokku.me" "https://$TEST_APP.dokku.me/" + assert_http_localhost_response "https" "$TEST_APP.dokku.me" "443" "" "python/http.server" +} diff --git a/tests/unit/scheduler-k3s-certs.bats b/tests/unit/scheduler-k3s-certs.bats new file mode 100755 index 00000000000..28115e16765 --- /dev/null +++ b/tests/unit/scheduler-k3s-certs.bats @@ -0,0 +1,140 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup_local_tls() { + TLS=$BATS_TMPDIR/tls + mkdir -p $TLS + tar xf $BATS_TEST_DIRNAME/server_ssl.tar -C $TLS + sudo chown -R dokku:dokku $TLS +} + +teardown_local_tls() { + TLS=$BATS_TMPDIR/tls + rm -R $TLS +} + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" + setup_local_tls +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true + teardown_local_tls +} + +@test "(scheduler-k3s:certs) certificate import creates k8s TLS secret" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler:set $TEST_APP selected k3s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Installing TLS certificate for $TEST_APP" + + run /bin/bash -c "kubectl get secret tls-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret tls-$TEST_APP -n default -o jsonpath='{.metadata.labels.dokku\\.com/cert-source}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "imported" +} + +@test "(scheduler-k3s:certs) certificate update updates k8s TLS secret" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler:set $TEST_APP selected k3s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + CHECKSUM1=$(kubectl get secret tls-$TEST_APP -n default -o jsonpath='{.metadata.labels.dokku\.com/cert-checksum}') + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + CHECKSUM2=$(kubectl get secret tls-$TEST_APP -n default -o jsonpath='{.metadata.labels.dokku\.com/cert-checksum}') + + [ "$CHECKSUM1" = "$CHECKSUM2" ] +} + +@test "(scheduler-k3s:certs) certificate removal deletes k8s TLS secret" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler:set $TEST_APP selected k3s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret tls-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:remove $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Removing TLS certificate for $TEST_APP" + + run /bin/bash -c "kubectl get secret tls-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_failure +} diff --git a/tests/unit/scheduler-k3s-charts-1.bats b/tests/unit/scheduler-k3s-charts-1.bats new file mode 100644 index 00000000000..a15c2326bde --- /dev/null +++ b/tests/unit/scheduler-k3s-charts-1.bats @@ -0,0 +1,107 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup +} + +teardown() { + dokku scheduler-k3s:charts:set cert-manager.installCRDs >/dev/null 2>/dev/null || true + dokku scheduler-k3s:charts:set cert-manager.version >/dev/null 2>/dev/null || true + dokku scheduler-k3s:charts:set traefik.replicas >/dev/null 2>/dev/null || true + global_teardown +} + +@test "(scheduler-k3s:charts:set) sets and unsets a chart property" { + run /bin/bash -c "dokku scheduler-k3s:charts:set cert-manager.installCRDs false" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:charts:report cert-manager --format json | jq -r '.\"cert-manager.installCRDs\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku scheduler-k3s:charts:report cert-manager" + assert_success + assert_output_contains "cert-manager chart information" + assert_output_contains "Chart property installCRDs:" + assert_output_contains "false" + + run /bin/bash -c "dokku scheduler-k3s:charts:set cert-manager.installCRDs" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:charts:report cert-manager --format json" + assert_success + assert_output "{}" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-chart.cert-manager.installCRDs\" // \"missing\"'" + assert_success + assert_output "missing" +} + +@test "(scheduler-k3s:charts:set) rejects invalid chart names and malformed property args" { + run /bin/bash -c "dokku scheduler-k3s:charts:set" + assert_failure + assert_output_contains "Invalid property" + + run /bin/bash -c "dokku scheduler-k3s:charts:set cert-manager" + assert_failure + assert_output_contains "Invalid property" + + run /bin/bash -c "dokku scheduler-k3s:charts:set .version 1.0" + assert_failure + assert_output_contains "Invalid property" + + run /bin/bash -c "dokku scheduler-k3s:charts:set cert-manager. 1.0" + assert_failure + assert_output_contains "Invalid property" + + run /bin/bash -c "dokku scheduler-k3s:charts:set not-a-chart.version 1.0" + assert_failure + assert_output_contains "Invalid chart name" + + run /bin/bash -c "dokku scheduler-k3s:set --global chart.cert-manager.version 1.13.3" + assert_success + assert_output_contains "deprecated" + + run /bin/bash -c "dokku scheduler-k3s:set --global chart.cert-manager.version" + assert_success +} + +@test "(scheduler-k3s:charts:report) per-chart, single-flag query, and flat JSON" { + run /bin/bash -c "dokku scheduler-k3s:charts:set cert-manager.version 1.13.3" + assert_success + run /bin/bash -c "dokku scheduler-k3s:charts:set traefik.replicas 2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:charts:report" + assert_success + assert_output_contains "cert-manager chart information" + assert_output_contains "traefik chart information" + assert_output_contains "longhorn chart information" + assert_output_contains "ingress-nginx chart information" + assert_output_contains "keda chart information" + assert_output_contains "keda-add-ons-http chart information" + assert_output_contains "vector chart information" + + run /bin/bash -c "dokku scheduler-k3s:charts:report cert-manager" + assert_success + assert_output_contains "cert-manager chart information" + assert_output_contains "Chart property version:" + assert_output_contains "1.13.3" + + run /bin/bash -c "dokku scheduler-k3s:charts:report cert-manager" + assert_output_not_contains "traefik chart information" + + run /bin/bash -c "dokku scheduler-k3s:charts:report --scheduler-k3s-charts-cert-manager.version" + assert_success + assert_output "1.13.3" + + run /bin/bash -c "dokku scheduler-k3s:charts:report --format json | jq -r '.\"cert-manager.version\"'" + assert_success + assert_output "1.13.3" + + run /bin/bash -c "dokku scheduler-k3s:charts:report --format json | jq -r '.\"traefik.replicas\"'" + assert_success + assert_output "2" +} diff --git a/tests/unit/scheduler-k3s-charts-2.bats b/tests/unit/scheduler-k3s-charts-2.bats new file mode 100644 index 00000000000..e56f3ea4a9f --- /dev/null +++ b/tests/unit/scheduler-k3s-charts-2.bats @@ -0,0 +1,57 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup +} + +teardown() { + dokku scheduler-k3s:charts:set traefik.service.annotations.prometheus.io/scrape >/dev/null 2>/dev/null || true + dokku scheduler-k3s:charts:set traefik.controller.config >/dev/null 2>/dev/null || true + dokku scheduler-k3s:charts:set cert-manager.installCRDs >/dev/null 2>/dev/null || true + global_teardown +} + +@test "(scheduler-k3s:charts:set) accepts property names containing /" { + run /bin/bash -c "dokku scheduler-k3s:charts:set traefik.service.annotations.prometheus.io/scrape true" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:charts:report traefik --format json | jq -r '.\"traefik.service.annotations.prometheus.io/scrape\"'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-k3s:charts:set traefik.service.annotations.prometheus.io/scrape" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:charts:report traefik --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:charts:set) preserves multi-line values" { + local value=$'line one\nline two\nline three' + run /bin/bash -c "dokku scheduler-k3s:charts:set traefik.controller.config \"$value\"" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:charts:report traefik --format json | jq -r '.\"traefik.controller.config\"'" + assert_success + assert_output "$value" +} + +@test "(scheduler-k3s:set) deprecated chart.* form writes through map storage" { + run /bin/bash -c "dokku scheduler-k3s:set --global chart.cert-manager.installCRDs false" + assert_success + assert_output_contains "deprecated" + + run /bin/bash -c "dokku scheduler-k3s:charts:report cert-manager --format json | jq -r '.\"cert-manager.installCRDs\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku scheduler-k3s:set --global chart.cert-manager.installCRDs" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:charts:report cert-manager --format json" + assert_success + assert_output "{}" +} diff --git a/tests/unit/scheduler-k3s-cnb.bats b/tests/unit/scheduler-k3s-cnb.bats new file mode 100644 index 00000000000..3c2bd8db572 --- /dev/null +++ b/tests/unit/scheduler-k3s-cnb.bats @@ -0,0 +1,295 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup_file() { + install_pack +} + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) cnb deployment sets command launcher for non-web process" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=1 worker=1" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt_cnb + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'from cnb stack' + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o=jsonpath='{.spec.template.spec.containers[0].command[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "launcher" + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o=jsonpath='{.spec.template.spec.containers[0].args[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "python3" +} + +@test "(scheduler-k3s) cnb cronjob manifest sets command launcher" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_wrapper + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'from cnb stack' + + run /bin/bash -c "kubectl get cronjob -o=jsonpath='{.items[0].spec.jobTemplate.spec.template.spec.containers[0].command[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "launcher" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + cron_hash="$(printf '%s' "$cron_id" | sha1sum | awk '{print $1}')" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.annotations.\"dokku.com/cron-id\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_id" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.labels.\"dokku.com/cron-hash\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_hash" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.annotations.\"dokku.com/cron-hash\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_hash" + + run /bin/bash -c "dokku --quiet cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "['task.py', 'some', 'cron', 'task']" +} + +@test "(scheduler-k3s) cnb dokku run uses launcher entrypoint" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt_cnb + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'from cnb stack' + + run /bin/bash -c "dokku --quiet run $TEST_APP python3 task.py test" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "['task.py', 'test']" + + run /bin/bash -c "dokku --quiet run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY=fjdkslafjdk" +} + +@test "(scheduler-k3s) cnb dokku run resolves Procfile key" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_procfile_wrapper + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'from cnb stack' + + run /bin/bash -c "dokku --quiet run $TEST_APP task" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "['task.py', 'test']" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku --quiet cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "['task.py', 'test']" +} + +@test "(scheduler-k3s) cnb dokku run:detached returns pod name with launcher entrypoint" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku builder:set $TEST_APP selected pack" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt_cnb + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains 'from cnb stack' + + run /bin/bash -c "dokku --quiet run:detached $TEST_APP sleep 300" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + pod_name="$output" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.metadata.labels.app\.kubernetes\.io/part-of}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.metadata.annotations.dokku\.com/builder-type}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "pack" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.spec.containers[0].command[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "launcher" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.spec.containers[0].args[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "sleep" +} + +cron_run_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + add_requirements_txt "$APP" "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cnb-cron.json" "$APP_REPO_DIR/app.json" +} + +cron_run_procfile_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + add_requirements_txt "$APP" "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cnb-cron-procfile.json" "$APP_REPO_DIR/app.json" +} diff --git a/tests/unit/scheduler-k3s-compression.bats b/tests/unit/scheduler-k3s-compression.bats new file mode 100644 index 00000000000..f759c8b3d20 --- /dev/null +++ b/tests/unit/scheduler-k3s-compression.bats @@ -0,0 +1,92 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup_local_tls() { + TLS=$BATS_TMPDIR/tls + mkdir -p $TLS + tar xf $BATS_TEST_DIRNAME/server_ssl.tar -C $TLS + sudo chown -R dokku:dokku $TLS +} + +teardown_local_tls() { + TLS=$BATS_TMPDIR/tls + rm -R $TLS +} + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" + setup_local_tls +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true + teardown_local_tls +} + +assert_http_localhost_header() { + local scheme="$1" domain="$2" port="${3:-80}" path="${4:-/}" accept_encoding="$5" expected_header="$6" + local retries="${HTTP_ASSERT_RETRIES:-30}" attempt=1 + + while [[ "$attempt" -lt "$retries" ]]; do + run /bin/bash -c "curl --connect-to '$domain:$port:localhost:$port' -kSsD - -o /dev/null -H 'Accept-Encoding: $accept_encoding' '$scheme://$domain:$port$path' | tr -d '\r' | grep -i '^$expected_header$'" + [[ "$status" -eq 0 ]] && break + sleep 1 + attempt=$((attempt + 1)) + done + + echo "output: $output" + echo "status: $status" + echo "attempts: $attempt" + assert_success + assert_output_contains "$expected_header" +} + +@test "(scheduler-k3s) [ingress] traefik compression middleware adds gzip response header" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=traefik install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/server.crt $BATS_TMPDIR/tls/server.key" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 30" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get ingressroutes.traefik.io ${TEST_APP}-web-http-80-5000 -n default -o jsonpath='{.spec.routes[0].middlewares[0].name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "${TEST_APP}-web-compression" + + assert_http_localhost_response "https" "$TEST_APP.dokku.me" "443" "" "python/http.server" + assert_http_localhost_header "https" "$TEST_APP.dokku.me" "443" "/" "gzip" "content-encoding: gzip" +} diff --git a/tests/unit/scheduler-k3s-dockerfile.bats b/tests/unit/scheduler-k3s-dockerfile.bats new file mode 100644 index 00000000000..83636ce6fee --- /dev/null +++ b/tests/unit/scheduler-k3s-dockerfile.bats @@ -0,0 +1,260 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) dockerfile deployment sets command for non-web process" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=1 worker=1" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile-procfile dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o=jsonpath='{.spec.template.spec.containers[0].command}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o=jsonpath='{.spec.template.spec.containers[0].args[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "node" + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o=jsonpath='{.spec.template.spec.containers[0].args[1]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "worker.js" +} + +@test "(scheduler-k3s) dockerfile cronjob manifest sets command" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile-procfile dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_wrapper + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get cronjob -o=jsonpath='{.items[0].spec.jobTemplate.spec.template.spec.containers[0].command}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get cronjob -o=jsonpath='{.items[0].spec.jobTemplate.spec.template.spec.containers[0].args[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "echo" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + cron_hash="$(printf '%s' "$cron_id" | sha1sum | awk '{print $1}')" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.annotations.\"dokku.com/cron-id\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_id" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.labels.\"dokku.com/cron-hash\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_hash" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.annotations.\"dokku.com/cron-hash\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_hash" + + run /bin/bash -c "dokku --quiet cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "hello-from-cron" +} + +@test "(scheduler-k3s) dockerfile dokku run executes command" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile-procfile dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run $TEST_APP echo hello-from-run" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "hello-from-run" + + run /bin/bash -c "dokku --quiet run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY=fjdkslafjdk" +} + +@test "(scheduler-k3s) dockerfile dokku run resolves Procfile key" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile-procfile dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_procfile_wrapper + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run $TEST_APP release" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY: fjdkslafjdk" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku --quiet cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY: fjdkslafjdk" +} + +@test "(scheduler-k3s) dockerfile dokku run:detached returns pod name" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app dockerfile dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run:detached $TEST_APP sleep 300" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + pod_name="$output" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.metadata.labels.app\.kubernetes\.io/part-of}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.metadata.annotations.dokku\.com/builder-type}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dockerfile" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.spec.containers[0].command}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" +} + +cron_run_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + mv -f "$APP_REPO_DIR/app-cron.json" "$APP_REPO_DIR/app.json" +} + +cron_run_procfile_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + mv -f "$APP_REPO_DIR/app-cron-procfile.json" "$APP_REPO_DIR/app.json" +} diff --git a/tests/unit/scheduler-k3s-herokuish.bats b/tests/unit/scheduler-k3s-herokuish.bats new file mode 100644 index 00000000000..ad8e4c2380b --- /dev/null +++ b/tests/unit/scheduler-k3s-herokuish.bats @@ -0,0 +1,267 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) herokuish deployment sets start command for non-web process" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=1 worker=1" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o=jsonpath='{.spec.template.spec.containers[0].command}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o=jsonpath='{.spec.template.spec.containers[0].args[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "/start" + + run /bin/bash -c "kubectl get deployment $TEST_APP-worker -o=jsonpath='{.spec.template.spec.containers[0].args[1]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "worker" +} + +@test "(scheduler-k3s) herokuish cronjob manifest sets start command" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_wrapper + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get cronjob -o=jsonpath='{.items[0].spec.jobTemplate.spec.template.spec.containers[0].command}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "kubectl get cronjob -o=jsonpath='{.items[0].spec.jobTemplate.spec.template.spec.containers[0].args[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "python3" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + cron_hash="$(printf '%s' "$cron_id" | sha1sum | awk '{print $1}')" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.annotations.\"dokku.com/cron-id\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_id" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.labels.\"dokku.com/cron-hash\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_hash" + + run /bin/bash -c "kubectl get cronjob -o json | jq -r '.items[0].metadata.annotations.\"dokku.com/cron-hash\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$cron_hash" + + run /bin/bash -c "dokku --quiet cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "['task.py', 'some', 'cron', 'task']" +} + +@test "(scheduler-k3s) herokuish dokku run uses /exec entrypoint" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run $TEST_APP python3 task.py test" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "['task.py', 'test']" + + run /bin/bash -c "dokku --quiet run $TEST_APP env" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "SECRET_KEY=fjdkslafjdk" +} + +@test "(scheduler-k3s) herokuish dokku run resolves Procfile key" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP SECRET_KEY=fjdkslafjdk" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP cron_run_procfile_wrapper + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run $TEST_APP task" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "['task.py', 'test']" + + cron_id="$(dokku cron:list $TEST_APP --format json | jq -r '.[0].id')" + run /bin/bash -c "echo $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + run /bin/bash -c "dokku --quiet cron:run $TEST_APP $cron_id" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "['task.py', 'test']" +} + +@test "(scheduler-k3s) herokuish dokku run:detached returns pod name" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku --quiet run:detached $TEST_APP sleep 300" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + pod_name="$output" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.metadata.labels.app\.kubernetes\.io/part-of}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$TEST_APP" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.metadata.annotations.dokku\.com/builder-type}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "herokuish" + + run /bin/bash -c "kubectl get pod $pod_name -o=jsonpath='{.spec.containers[0].command[0]}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "/exec" +} + +cron_run_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + add_requirements_txt "$APP" "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cron.json" "$APP_REPO_DIR/app.json" +} + +cron_run_procfile_wrapper() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + add_requirements_txt "$APP" "$APP_REPO_DIR" + mv -f "$APP_REPO_DIR/app-cron-procfile.json" "$APP_REPO_DIR/app.json" +} diff --git a/tests/unit/scheduler-k3s-labels.bats b/tests/unit/scheduler-k3s-labels.bats new file mode 100644 index 00000000000..ae007b45c96 --- /dev/null +++ b/tests/unit/scheduler-k3s-labels.bats @@ -0,0 +1,115 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku apps:create $TEST_APP >/dev/null 2>/dev/null || true +} + +teardown() { + dokku --force apps:destroy $TEST_APP >/dev/null 2>/dev/null || true + dokku --force apps:destroy ${TEST_APP}-2 >/dev/null 2>/dev/null || true + for resource in deployment service pod cronjob job secret ingress serviceaccount; do + dokku scheduler-k3s:labels:set --global --resource-type "$resource" foo "" >/dev/null 2>/dev/null || true + dokku scheduler-k3s:labels:set --global --resource-type "$resource" bar "" >/dev/null 2>/dev/null || true + done + global_teardown +} + +@test "(scheduler-k3s:labels:report) lists labels after set, clears them after unset" { + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo bar" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format json | jq -r '.\"global.deployment.foo\"'" + assert_success + assert_output "bar" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP" + assert_success + assert_output_contains "$TEST_APP labels information" + assert_output_contains "Label (global/deployment) foo:" + assert_output_contains "bar" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --scheduler-k3s-labels.global.deployment.foo" + assert_success + assert_output "bar" + + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format json" + assert_success + assert_output "{}" +} + +@test "(scheduler-k3s:labels:report) honours --process-type and --resource-type filters" { + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo bar" + assert_success + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --process-type web --resource-type deployment baz qux" + assert_success + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type service alpha beta" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --resource-type deployment --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "global.deployment.foo,web.deployment.baz" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --process-type web --format json | jq -r 'keys | sort | join(\",\")'" + assert_success + assert_output "web.deployment.baz" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --process-type web --resource-type deployment --format json | jq -r '.\"web.deployment.baz\"'" + assert_success + assert_output "qux" +} + +@test "(scheduler-k3s:labels:report) supports --global scope and no-arg multi-app loop" { + run /bin/bash -c "dokku apps:create ${TEST_APP}-2" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:set --global --resource-type deployment foo global-val" + assert_success + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo app1-val" + assert_success + run /bin/bash -c "dokku scheduler-k3s:labels:set ${TEST_APP}-2 --resource-type deployment bar app2-val" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report --global --format json | jq -r '.\"global.deployment.foo\"'" + assert_success + assert_output "global-val" + + run /bin/bash -c "dokku scheduler-k3s:labels:report" + assert_success + assert_output_contains "$TEST_APP labels information" + assert_output_contains "${TEST_APP}-2 labels information" + assert_output_contains "app1-val" + assert_output_contains "app2-val" +} + +@test "(scheduler-k3s:labels:set) preserves multi-line values and / in keys" { + local value=$'line one\nline two\nline three' + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment app.kubernetes.io/part-of \"$value\"" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format json | jq -r '.\"global.deployment.app.kubernetes.io/part-of\"'" + assert_success + assert_output "$value" +} + +@test "(scheduler-k3s:labels:report) rejects invalid format and info-flag combinations" { + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format yaml" + assert_failure + assert_output_contains "Invalid format" + + run /bin/bash -c "dokku scheduler-k3s:labels:set $TEST_APP --resource-type deployment foo bar" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --format json --scheduler-k3s-labels.global.deployment.foo" + assert_failure + assert_output_contains "--format flag cannot be specified when specifying an info flag" + + run /bin/bash -c "dokku scheduler-k3s:labels:report $TEST_APP --scheduler-k3s-labels.global.deployment.missing" + assert_failure + assert_output_contains "Invalid flag passed" +} diff --git a/tests/unit/scheduler-k3s-preview.bats b/tests/unit/scheduler-k3s-preview.bats new file mode 100644 index 00000000000..6a9273b9dd2 --- /dev/null +++ b/tests/unit/scheduler-k3s-preview.bats @@ -0,0 +1,91 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s:preview) clean redeploy shows no diff" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:preview $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" +} + +@test "(scheduler-k3s:preview) resource limit change appears in diff" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku resource:limit --process-type web --memory 256 $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:preview $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "has changed" + assert_output_contains "256Mi" +} + +@test "(scheduler-k3s:preview) undeployed app shows additive diff" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:preview $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "has been added" -1 +} diff --git a/tests/unit/scheduler-k3s-report.bats b/tests/unit/scheduler-k3s-report.bats new file mode 100644 index 00000000000..fed4e8255c9 --- /dev/null +++ b/tests/unit/scheduler-k3s-report.bats @@ -0,0 +1,209 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup +} + +teardown() { + for prop in deploy-timeout image-pull-secrets ingress-class kubeconfig-path kube-context kustomize-root-path namespace network-interface rollback-on-failure shm-size token; do + dokku scheduler-k3s:set --global "$prop" >/dev/null 2>/dev/null || true + done + global_teardown +} + +# assert_k3s_global_unset_set verifies that a global property cycles correctly +# through the raw/computed split: when unset, the global key is empty and the +# computed key returns the built-in default; after setting, both pair members +# match the set value. Excludes letsencrypt-* properties whose set path applies +# cluster issuers via helm and requires a live k3s cluster. +assert_k3s_global_unset_set() { + declare prop="$1" expected_default="$2" set_value="$3" + + run /bin/bash -c "dokku scheduler-k3s:set --global $prop" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-$prop\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-$prop\"'" + assert_success + assert_output "$expected_default" + + run /bin/bash -c "dokku scheduler-k3s:set --global $prop '$set_value'" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-$prop\"'" + assert_success + assert_output "$set_value" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-$prop\"'" + assert_success + assert_output "$set_value" + + run /bin/bash -c "dokku scheduler-k3s:set --global $prop" + assert_success +} + +@test "(scheduler-k3s:report --global) defaulted properties raw/computed cycle" { + assert_k3s_global_unset_set "deploy-timeout" "300s" "600s" + assert_k3s_global_unset_set "kustomize-root-path" "config/kustomize" "deploy/kustomize" + assert_k3s_global_unset_set "namespace" "default" "production" + assert_k3s_global_unset_set "rollback-on-failure" "false" "true" + assert_k3s_global_unset_set "ingress-class" "nginx" "traefik" + assert_k3s_global_unset_set "network-interface" "eth0" "eth1" + assert_k3s_global_unset_set "kubeconfig-path" "/etc/rancher/k3s/k3s.yaml" "/tmp/custom-kubeconfig.yaml" +} + +@test "(scheduler-k3s:report --global) empty-default properties expose computed sibling" { + for prop in image-pull-secrets shm-size kube-context; do + run /bin/bash -c "dokku scheduler-k3s:set --global $prop" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-$prop\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-$prop\"'" + assert_success + assert_output "" + done + + run /bin/bash -c "dokku scheduler-k3s:set --global image-pull-secrets my-secret" + assert_success + run /bin/bash -c "dokku scheduler-k3s:set --global shm-size 64Mi" + assert_success + run /bin/bash -c "dokku scheduler-k3s:set --global kube-context my-context" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-image-pull-secrets\"'" + assert_success + assert_output "my-secret" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-image-pull-secrets\"'" + assert_success + assert_output "my-secret" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-shm-size\"'" + assert_success + assert_output "64Mi" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-shm-size\"'" + assert_success + assert_output "64Mi" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-kube-context\"'" + assert_success + assert_output "my-context" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-kube-context\"'" + assert_success + assert_output "my-context" +} + +@test "(scheduler-k3s:report --global) letsencrypt properties unset state" { + for prop in letsencrypt-server letsencrypt-email-prod letsencrypt-email-stag; do + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-$prop\"'" + assert_success + assert_output "" + done + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-letsencrypt-server\"'" + assert_success + assert_output "prod" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-letsencrypt-email-prod\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-computed-letsencrypt-email-stag\"'" + assert_success + assert_output "" +} + +@test "(scheduler-k3s:report) app-level letsencrypt email overrides global and falls back when unset" { + run /bin/bash -c "dokku apps:create $TEST_APP" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:set $TEST_APP letsencrypt-email-prod team@dokku.me" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:report $TEST_APP --format json | jq -r '.\"scheduler-k3s-letsencrypt-email-prod\"'" + assert_success + assert_output "team@dokku.me" + + run /bin/bash -c "dokku scheduler-k3s:report $TEST_APP --format json | jq -r '.\"scheduler-k3s-computed-letsencrypt-email-prod\"'" + assert_success + assert_output "team@dokku.me" + + run /bin/bash -c "dokku scheduler-k3s:report $TEST_APP --scheduler-k3s-letsencrypt-email-prod" + assert_success + assert_output "team@dokku.me" + + run /bin/bash -c "dokku scheduler-k3s:set $TEST_APP letsencrypt-email-prod" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:report $TEST_APP --format json | jq -r '.\"scheduler-k3s-letsencrypt-email-prod\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-k3s:report $TEST_APP --format json | jq -r '.\"scheduler-k3s-computed-letsencrypt-email-prod\"'" + assert_success + assert_output "" +} + +@test "(scheduler-k3s:report --global) token masked in stdout but exposed via json or explicit flag" { + run /bin/bash -c "dokku scheduler-k3s:set --global token" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -e 'has(\"scheduler-k3s-global-token\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-token\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-k3s:report --global --scheduler-k3s-global-token" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler-k3s:set --global token sometoken" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:report --global" + assert_success + assert_output_contains "*******" + assert_output_not_contains "sometoken" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r '.\"scheduler-k3s-global-token\"'" + assert_success + assert_output "sometoken" + + run /bin/bash -c "dokku scheduler-k3s:report --global --scheduler-k3s-global-token" + assert_success + assert_output "sometoken" + + run /bin/bash -c "dokku scheduler-k3s:set --global token" + assert_success +} + +@test "(scheduler-k3s:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r 'has(\"global-deploy-timeout\") and has(\"scheduler-k3s-global-deploy-timeout\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r 'has(\"computed-deploy-timeout\") and has(\"scheduler-k3s-computed-deploy-timeout\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r 'has(\"global-namespace\") and has(\"scheduler-k3s-global-namespace\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler-k3s:report --global --format json | jq -r 'has(\"computed-namespace\") and has(\"scheduler-k3s-computed-namespace\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/scheduler-k3s-secrets-deploy.bats b/tests/unit/scheduler-k3s-secrets-deploy.bats new file mode 100644 index 00000000000..58295bb9503 --- /dev/null +++ b/tests/unit/scheduler-k3s-secrets-deploy.bats @@ -0,0 +1,166 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s:secrets) helm rollback of app chart leaves config and pull secret releases intact" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP HELLO=world" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 30" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "helm rollback $TEST_APP 1 -n default" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret config-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret pull-secret-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment ${TEST_APP}-web -n default -o jsonpath='{.spec.template.spec.containers[0].envFrom[0].secretRef.name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "config-$TEST_APP" +} + +@test "(scheduler-k3s:secrets) user image-pull-secrets override skips dokku-managed pull secret release" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:set $TEST_APP image-pull-secrets my-custom-pull-secret" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret pull-secret-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "kubectl get deployment ${TEST_APP}-web -n default -o jsonpath='{.spec.template.spec.imagePullSecrets[*].name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "my-custom-pull-secret" +} + +@test "(scheduler-k3s:secrets) leaked imagePullSecrets entries get pruned on next deploy" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl patch deployment ${TEST_APP}-web -n default --type=json -p='[{\"op\":\"add\",\"path\":\"/spec/template/spec/imagePullSecrets/-\",\"value\":{\"name\":\"ims-${TEST_APP}.111\"}},{\"op\":\"add\",\"path\":\"/spec/template/spec/imagePullSecrets/-\",\"value\":{\"name\":\"ims-${TEST_APP}.222\"}}]'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment ${TEST_APP}-web -n default -o jsonpath='{.spec.template.spec.imagePullSecrets[*].name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "ims-${TEST_APP}.111" + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 30" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment ${TEST_APP}-web -n default -o jsonpath='{.spec.template.spec.imagePullSecrets[*].name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "pull-secret-$TEST_APP" +} diff --git a/tests/unit/scheduler-k3s-secrets.bats b/tests/unit/scheduler-k3s-secrets.bats new file mode 100644 index 00000000000..830e6759add --- /dev/null +++ b/tests/unit/scheduler-k3s-secrets.bats @@ -0,0 +1,162 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s:secrets) deploy creates stable config and pull secrets" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP HELLO=world" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret config-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret pull-secret-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment ${TEST_APP}-web -n default -o jsonpath='{.spec.template.spec.containers[0].envFrom[0].secretRef.name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "config-$TEST_APP" + + run /bin/bash -c "kubectl get deployment ${TEST_APP}-web -n default -o jsonpath='{.spec.template.spec.imagePullSecrets[*].name}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "pull-secret-$TEST_APP" + + run /bin/bash -c "kubectl get secret config-$TEST_APP -n default -o jsonpath='{.data.HELLO}' | base64 --decode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "world" +} + +@test "(scheduler-k3s:secrets) config:set updates secret in place" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set --no-restart $TEST_APP HELLO=world" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:set $TEST_APP HELLO=mars" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sleep 10" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret config-$TEST_APP -n default -o jsonpath='{.data.HELLO}' | base64 --decode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "mars" +} + +@test "(scheduler-k3s:secrets) apps:destroy cleans up secret releases" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python "dokku@$DOKKU_DOMAIN:$TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret config-$TEST_APP -n default" + assert_success + + run /bin/bash -c "kubectl get secret pull-secret-$TEST_APP -n default" + assert_success + + run /bin/bash -c "dokku --force apps:destroy $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get secret config-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "kubectl get secret pull-secret-$TEST_APP -n default" + echo "output: $output" + echo "status: $status" + assert_failure +} diff --git a/tests/unit/scheduler.bats b/tests/unit/scheduler.bats new file mode 100644 index 00000000000..2f110894a78 --- /dev/null +++ b/tests/unit/scheduler.bats @@ -0,0 +1,165 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + dokku scheduler:set --global selected >/dev/null 2>&1 || true + dokku scheduler:set --global shell >/dev/null 2>&1 || true + destroy_app + global_teardown +} + +@test "(scheduler) scheduler:help" { + run /bin/bash -c "dokku scheduler" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage scheduler settings" + help_output="$output" + + run /bin/bash -c "dokku scheduler:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage scheduler settings" + assert_output "$help_output" +} + +@test "(scheduler:report) selected raw vs computed vs global" { + run /bin/bash -c "dokku scheduler:set --global selected" + assert_success + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-selected\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-global-selected\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-computed-selected\"'" + assert_success + assert_output "docker-local" + + run /bin/bash -c "dokku scheduler:set --global selected null" + assert_success + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-global-selected\"'" + assert_success + assert_output "null" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-computed-selected\"'" + assert_success + assert_output "null" + + run /bin/bash -c "dokku scheduler:set $TEST_APP selected docker-local" + assert_success + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-selected\"'" + assert_success + assert_output "docker-local" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-global-selected\"'" + assert_success + assert_output "null" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-computed-selected\"'" + assert_success + assert_output "docker-local" + + run /bin/bash -c "dokku scheduler:set $TEST_APP selected" + assert_success + + run /bin/bash -c "dokku scheduler:set --global selected" + assert_success +} + +@test "(scheduler:report) shell raw vs computed vs global" { + run /bin/bash -c "dokku scheduler:set --global shell" + assert_success + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-shell\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-global-shell\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-computed-shell\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler:set --global shell sh" + assert_success + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-global-shell\"'" + assert_success + assert_output "sh" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-computed-shell\"'" + assert_success + assert_output "sh" + + run /bin/bash -c "dokku scheduler:set $TEST_APP shell bash" + assert_success + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-shell\"'" + assert_success + assert_output "bash" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-global-shell\"'" + assert_success + assert_output "sh" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r '.\"scheduler-computed-shell\"'" + assert_success + assert_output "bash" + + run /bin/bash -c "dokku scheduler:set $TEST_APP shell" + assert_success + + run /bin/bash -c "dokku scheduler:set --global shell" + assert_success +} + +@test "(scheduler:report) --global --format json" { + run /bin/bash -c "dokku scheduler:set --global selected" + assert_success + + run /bin/bash -c "dokku scheduler:report --global --format json | jq -e ." + assert_success + + run /bin/bash -c "dokku scheduler:report --global --format json | jq -r '.\"scheduler-global-selected\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku scheduler:report --global --format json | jq -r '.\"scheduler-computed-selected\"'" + assert_success + assert_output "docker-local" + + run /bin/bash -c "dokku scheduler:report --global --format json | jq -r 'has(\"scheduler-selected\")'" + assert_success + assert_output "false" +} + +@test "(scheduler:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r 'has(\"selected\") and has(\"scheduler-selected\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r 'has(\"global-selected\") and has(\"scheduler-global-selected\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler:report $TEST_APP --format json | jq -r 'has(\"computed-selected\") and has(\"scheduler-computed-selected\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku scheduler:report --global --format json | jq -r 'has(\"global-selected\") and has(\"scheduler-global-selected\")'" + assert_success + assert_output "true" +} diff --git a/tests/unit/server_ssl.tar b/tests/unit/server_ssl.tar index 855a332104c..6cf6df7ede0 100644 Binary files a/tests/unit/server_ssl.tar and b/tests/unit/server_ssl.tar differ diff --git a/tests/unit/server_ssl_sans.tar b/tests/unit/server_ssl_sans.tar index ae50b7a9a46..a472b46e1c1 100644 Binary files a/tests/unit/server_ssl_sans.tar and b/tests/unit/server_ssl_sans.tar differ diff --git a/tests/unit/server_ssl_wildcard.tar b/tests/unit/server_ssl_wildcard.tar index 0fa52c4eefa..fa45381ac2d 100644 Binary files a/tests/unit/server_ssl_wildcard.tar and b/tests/unit/server_ssl_wildcard.tar differ diff --git a/tests/unit/ssh_keys.bats b/tests/unit/ssh_keys.bats index 37f079c1ecd..33dc3a2ee24 100644 --- a/tests/unit/ssh_keys.bats +++ b/tests/unit/ssh_keys.bats @@ -14,14 +14,17 @@ setup() { create_key # Test key without a trailing newline - echo -n "$KEY" > /tmp/testkey-no-newline.pub + echo -n "$KEY" >/tmp/testkey-no-newline.pub # the temporary key is useful for adding in the file with two keys # useful for a negative test - { cat /tmp/testkey.pub ; echo "$KEY" ; } > /tmp/testkey-double.pub + { + cat /tmp/testkey.pub + echo "$KEY" + } >/tmp/testkey-double.pub # another negative test input - echo 'invalid!' > /tmp/testkey-invalid.pub + echo 'invalid!' >/tmp/testkey-invalid.pub # save current authorized_keys to remove all changes afterwards cp "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys" /tmp/testkey-authorized_keys @@ -294,7 +297,6 @@ teardown() { assert_equal "$output" "1" } - @test "(ssh-keys) ssh-keys:list --format invalid" { run /bin/bash -c "dokku ssh-keys:list --format invalid" echo "output: $output" diff --git a/tests/unit/storage.bats b/tests/unit/storage.bats index 87ae535821f..3acc757bce9 100644 --- a/tests/unit/storage.bats +++ b/tests/unit/storage.bats @@ -90,7 +90,7 @@ teardown() { assert_output_contains "Setting directory ownership to 2000:2000" 0 assert_output_contains "Setting directory ownership to 32767:32767" 0 - run /bin/bash -c "dokku storage:ensure-directory --chown packeto $TEST_APP" + run /bin/bash -c "dokku storage:ensure-directory --chown paketo $TEST_APP" echo "output: $output" echo "status: $status" assert_success @@ -107,35 +107,849 @@ teardown() { assert_output_contains "Setting directory ownership to 32767:32767" 1 } +@test "(storage) chown-storage-dir rejects out-of-bounds chown values" { + run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP 65536" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Unsupported chown permissions" + + run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP 165535" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Unsupported chown permissions" + + run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP 231072" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Unsupported chown permissions" + + run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP -1" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Value must be a non-negative integer" + + run /bin/bash -c "DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_AVAILABLE_PATH/storage/bin/chown-storage-dir $TEST_APP abc" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Value must be a non-negative integer" +} + @test "(storage) storage:mount, storage:list, storage:umount" { run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/mount:/mount" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku storage:list $TEST_APP | grep -qe '^\s*/tmp/mount:/mount'" + + run /bin/bash -c "dokku --quiet storage:list $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "/tmp/mount:/mount" + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].host_path'" echo "output: $output" echo "status: $status" assert_success + assert_output "/tmp/mount" + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].container_path'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "/mount" + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r 'map(has(\"volume_options\") or has(\"readonly\")) | any'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/mount:/mount" echo "output: $output" echo "status: $status" assert_output " ! Mount path already exists." assert_failure + run /bin/bash -c "dokku storage:unmount $TEST_APP /tmp/mount:/mount" echo "output: $output" echo "status: $status" assert_success - run /bin/bash -c "dokku storage:list $TEST_APP | grep -vqe '^\s*/tmp/mount:/mount'" + + run /bin/bash -c "dokku --quiet storage:list $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" echo "output: $output" echo "status: $status" assert_success + assert_output "0" + run /bin/bash -c "dokku storage:unmount $TEST_APP /tmp/mount:/mount" echo "output: $output" echo "status: $status" assert_output " ! Mount path does not exist." assert_failure + run /bin/bash -c "dokku storage:mount $TEST_APP mount_volume:/mount" echo "output: $output" echo "status: $status" assert_success + + run /bin/bash -c "dokku --quiet storage:list $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "mount_volume:/mount" +} + +@test "(storage:report) build/deploy/run mounts raw" { + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-build-mounts\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-deploy-mounts\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-run-mounts\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'" + assert_success + assert_output "0" + + run /bin/bash -c "dokku storage:mount $TEST_APP /tmp/storage-mount:/mount" + assert_success + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-deploy-mounts\"'" + assert_success + assert_output "-v /tmp/storage-mount:/mount" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-run-mounts\"'" + assert_success + assert_output "-v /tmp/storage-mount:/mount" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-build-mounts\"'" + assert_success + assert_output "" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'" + assert_success + assert_output "9" + + run /bin/bash -c "dokku storage:unmount $TEST_APP /tmp/storage-mount:/mount" + assert_success +} + +@test "(storage:report) emits per-attachment dotted keys" { + run /bin/bash -c "dokku storage:create rdmtest-rpt" + assert_success + + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt --container-dir /data --phase deploy --phase run --volume-options Z --volume-chown herokuish --volume-subpath uploads" + assert_success + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.entry-name\"'" + assert_success + assert_output "rdmtest-rpt" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.host-path\"'" + assert_success + assert_output "$DOKKU_LIB_ROOT/data/storage/rdmtest-rpt" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.container-path\"'" + assert_success + assert_output "/data" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.phases\"'" + assert_success + assert_output "deploy,run" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.process-type\"'" + assert_success + assert_output "_default_" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.subpath\"'" + assert_success + assert_output "uploads" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.readonly\"'" + assert_success + assert_output "false" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.volume-options\"'" + assert_success + assert_output "Z" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"attachment.1.volume-chown\"'" + assert_success + assert_output "herokuish" + + # stdout output gains one line per field; verify a representative subset + run /bin/bash -c "dokku storage:report $TEST_APP" + assert_success + assert_output_contains "Storage attachment 1 entry name" + assert_output_contains "rdmtest-rpt" -1 + assert_output_contains "Storage attachment 1 volume options" + assert_output_contains "Storage attachment 1 volume chown" + assert_output_contains "herokuish" + + # info-flag lookup returns just the value + run /bin/bash -c "dokku storage:report $TEST_APP --storage-attachment.1.volume-options" + assert_success + assert_output "Z" + + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt --container-dir /data" + assert_success + run /bin/bash -c "dokku storage:destroy rdmtest-rpt --force" + assert_success +} + +@test "(storage:report) multiple attachments cluster by index" { + run /bin/bash -c "dokku storage:create rdmtest-rpt-a" + assert_success + run /bin/bash -c "dokku storage:create rdmtest-rpt-b" + assert_success + + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-a --container-dir /data --volume-options Z" + assert_success + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-b --container-dir /cache --volume-options noexec,nosuid" + assert_success + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[.\"attachment.1.entry-name\", .\"attachment.2.entry-name\"] | sort | join(\"|\")'" + assert_success + assert_output "rdmtest-rpt-a|rdmtest-rpt-b" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '[.\"attachment.1.volume-options\", .\"attachment.2.volume-options\"] | sort | join(\"|\")'" + assert_success + assert_output "Z|noexec,nosuid" + + # info-flag lookup against the second attachment + run /bin/bash -c "dokku storage:report $TEST_APP --storage-attachment.2.entry-name" + assert_success + assert_output_contains "rdmtest-rpt" + + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-a --container-dir /data" + assert_success + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-b --container-dir /cache" + assert_success + run /bin/bash -c "dokku storage:destroy rdmtest-rpt-a --force" + assert_success + run /bin/bash -c "dokku storage:destroy rdmtest-rpt-b --force" + assert_success +} + +@test "(storage:report) degrades gracefully when an attachment entry is missing" { + run /bin/bash -c "dokku storage:create rdmtest-rpt-missing" + assert_success + + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-missing --container-dir /data --volume-options Z" + assert_success + + # delete the entry on disk so LoadEntry fails for the existing attachment + run /bin/bash -c "sudo rm -f $DOKKU_LIB_ROOT/data/storage-registry/entries/rdmtest-rpt-missing.json" + assert_success + + # discard dokku's stderr so the LogWarn line doesn't pollute the jq output capture + run /bin/bash -c "dokku storage:report $TEST_APP --format json 2>/dev/null | jq -r '[keys[] | select(startswith(\"attachment.\"))] | length'" + assert_success + assert_output "0" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json 2>/dev/null | jq -r 'has(\"storage-build-mounts\") and has(\"storage-deploy-mounts\") and has(\"storage-run-mounts\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku storage:report $TEST_APP" + assert_success + assert_output_contains "Skipping attachment" + assert_output_contains "rdmtest-rpt-missing" -1 + assert_output_contains "Storage build mounts" + assert_output_contains "Storage deploy mounts" + assert_output_contains "Storage run mounts" + + # clean up the leftover attachment record so subsequent tests don't trip + run /bin/bash -c "sudo rm -f $DOKKU_LIB_ROOT/config/storage/$TEST_APP/attachments" + assert_success +} + +@test "(storage) storage:create / storage:list-entries / storage:destroy" { + run /bin/bash -c "dokku storage:create rdmtest-entry" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-entry$'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "rdmtest-entry" + + run /bin/bash -c "dokku storage:info rdmtest-entry --format json | jq -r '.scheduler'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "docker-local" + + run /bin/bash -c "dokku storage:destroy rdmtest-entry --force" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(storage:create) --chown sets directory ownership" { + run /bin/bash -c "dokku storage:create --chown herokuish rdmtest-chown" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "stat -c '%u:%g' $DOKKU_LIB_ROOT/data/storage/rdmtest-chown" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "32767:32767" + + run /bin/bash -c "dokku storage:destroy rdmtest-chown --force" + assert_success +} + +@test "(storage:create) --chown accepts a custom numeric uid" { + run /bin/bash -c "dokku storage:create --chown 1500 rdmtest-chown-numeric" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "stat -c '%u:%g' $DOKKU_LIB_ROOT/data/storage/rdmtest-chown-numeric" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1500:1500" + + run /bin/bash -c "dokku storage:destroy rdmtest-chown-numeric --force" + assert_success +} + +@test "(storage:create) --chown rejects an out-of-bounds numeric uid" { + run /bin/bash -c "dokku storage:create --chown 65536 rdmtest-chown-oob" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Unsupported chown permissions" + + run /bin/bash -c "dokku storage:create --chown -1 rdmtest-chown-oob" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Unsupported chown permissions" + + run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-chown-oob$' || true" + assert_output "" +} + +@test "(storage:create) --chown rejects a non-default host path" { + custom_path="/tmp/rdmtest-chown-custom" + rm -rf "$custom_path" + + run /bin/bash -c "dokku storage:create --chown herokuish rdmtest-chown-custom $custom_path" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "--chown is only supported when the storage entry uses the default host path" + + run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-chown-custom$' || true" + assert_output "" + + rm -rf "$custom_path" +} + +@test "(storage) storage:create rejects invalid names" { + # underscore: rejected + run /bin/bash -c "dokku storage:create rdmtest_invalid" + echo "output: $output" + echo "status: $status" + assert_failure + + # uppercase: rejected + run /bin/bash -c "dokku storage:create RdmTest" + echo "output: $output" + echo "status: $status" + assert_failure + + # 46 chars: too long + long_name=$(printf 'a%.0s' {1..46}) + run /bin/bash -c "dokku storage:create $long_name" + echo "output: $output" + echo "status: $status" + assert_failure + + # legacy- prefix: reserved + run /bin/bash -c "dokku storage:create legacy-foo" + echo "output: $output" + echo "status: $status" + assert_failure +} + +@test "(storage) storage:create + storage:mount with named entry attaches multiple entries to one app" { + run /bin/bash -c "dokku storage:create rdmtest-data" + assert_success + run /bin/bash -c "dokku storage:create rdmtest-cache" + assert_success + + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-data --container-dir /data" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-cache --container-dir /cache" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].entry_name' | sort | xargs" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "rdmtest-cache rdmtest-data" + + # cleanup + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-data" + assert_success + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-cache" + assert_success + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "0" + + run /bin/bash -c "dokku storage:destroy rdmtest-data --force" + assert_success + run /bin/bash -c "dokku storage:destroy rdmtest-cache --force" + assert_success +} + +@test "(storage:mount) --volume-options sets option on named-entry attachment" { + run /bin/bash -c "dokku storage:create rdmtest-opts" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-opts --container-dir /data --volume-options Z" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].volume_options'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "Z" + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].container_path'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "/data" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-deploy-mounts\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains ":Z" + + # combine with --volume-readonly: rendered as ro, + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-opts --container-dir /ro --volume-options noexec,nosuid --volume-readonly" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r '.\"storage-deploy-mounts\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains ":ro,noexec,nosuid" + + # The JSON output of storage:list exposes readonly and volume_options + # as separate keys so external drift-detection tooling can compare + # them against the underlying attachment fields one for one. + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[] | select(.container_path == \"/ro\") | .readonly'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[] | select(.container_path == \"/ro\") | .volume_options'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "noexec,nosuid" + + # The /data mount has no readonly flag, so the key is absent. + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[] | select(.container_path == \"/data\") | has(\"readonly\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + # cleanup + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-opts --container-dir /data" + assert_success + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-opts --container-dir /ro" + assert_success + run /bin/bash -c "dokku storage:destroy rdmtest-opts --force" + assert_success +} + +@test "(storage:mount) re-running against an existing (entry, container-dir) updates in place" { + run /bin/bash -c "dokku storage:create rdmtest-upsert" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /data --volume-options Z" + echo "output: $output" + echo "status: $status" + assert_success + + # Re-mount the same (entry, container-dir) with a different --volume-options. + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /data --volume-options noexec,nosuid" + echo "output: $output" + echo "status: $status" + assert_success + + # Single attachment, not two. + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1" + + # Latest --volume-options wins. + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].volume_options'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "noexec,nosuid" + + # Re-mount with --volume-readonly added; readonly toggles on. + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /data --volume-options noexec,nosuid --volume-readonly" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[].readonly'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "true" + + # Re-mount without --volume-readonly: mount-time fields are rewritten, + # not merged, so the readonly key disappears from the JSON output. + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /data --volume-options noexec,nosuid" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '.[] | has(\"readonly\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + # A different --container-dir on the same entry is still a distinct attachment. + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-upsert --container-dir /other" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2" + + # cleanup + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-upsert --container-dir /data" + assert_success + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-upsert --container-dir /other" + assert_success + run /bin/bash -c "dokku storage:destroy rdmtest-upsert --force" + assert_success +} + +@test "(storage) storage:destroy refuses to remove a still-mounted entry" { + run /bin/bash -c "dokku storage:create rdmtest-busy" + assert_success + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-busy --container-dir /data" + assert_success + + run /bin/bash -c "dokku storage:destroy rdmtest-busy" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "still mounted" + + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-busy" + assert_success + run /bin/bash -c "dokku storage:destroy rdmtest-busy --force" + assert_success +} + +@test "(storage:destroy) requires confirmation without --force" { + run /bin/bash -c "dokku storage:create rdmtest-confirm" + assert_success + + # No --force and no matching stdin: aborts, entry remains. + run /bin/bash -c "dokku storage:destroy rdmtest-confirm < /dev/null" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-confirm$'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "rdmtest-confirm" + + # Matching confirmation via stdin: succeeds and removes the entry. + run /bin/bash -c "echo rdmtest-confirm | dokku storage:destroy rdmtest-confirm" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-confirm$' || true" + echo "output: $output" + echo "status: $status" + assert_output "" +} + +@test "(storage:destroy) --force skips confirmation" { + run /bin/bash -c "dokku storage:create rdmtest-force" + assert_success + + run /bin/bash -c "dokku storage:destroy rdmtest-force --force < /dev/null" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-force$' || true" + assert_output "" +} + +@test "(storage:destroy) global --force skips confirmation" { + run /bin/bash -c "dokku storage:create rdmtest-gforce" + assert_success + + run /bin/bash -c "dokku --force storage:destroy rdmtest-gforce < /dev/null" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^rdmtest-gforce$' || true" + assert_output "" +} + +@test "(storage) storage:exec runs a non-interactive command and propagates exit code" { + run /bin/bash -c "dokku storage:create rdmtest-exec" + assert_success + + # docker pull image up-front so the test isn't gated on registry latency. + run /bin/bash -c "docker pull alpine:3 >/dev/null" + assert_success + + run /bin/bash -c "dokku storage:exec rdmtest-exec -- /bin/sh -c 'touch /data/marker && ls /data/marker'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "/data/marker" + + run /bin/bash -c "dokku storage:exec rdmtest-exec -- /bin/sh -c 'exit 42'" + echo "output: $output" + echo "status: $status" + assert_equal "$status" 42 + + run /bin/bash -c "dokku storage:destroy rdmtest-exec --force" + assert_success +} + +@test "(storage) storage:migrate converts legacy docker-options -v lines into attachments" { + # Stage a legacy `-v` line directly via docker-options:add. This + # bypasses the new storage:mount path so the line lives in + # docker-options without a corresponding attachment, simulating an + # app upgraded from a pre-PR Dokku version. + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy,run \"-v /tmp/legacy-mount:/legacy\"" + echo "output: $output" + echo "status: $status" + assert_success + + # Before migration, storage:list shows nothing (attachment-only) and + # docker-options has the -v line. + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" + assert_success + assert_output "0" + + run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-deploy" + assert_success + assert_output_contains "-v /tmp/legacy-mount:/legacy" + + # Run the migration for this app. + run /bin/bash -c "dokku storage:migrate $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + # storage:list now surfaces the synthesized colon form. + run /bin/bash -c "dokku --quiet storage:list $TEST_APP" + assert_success + assert_output "/tmp/legacy-mount:/legacy" + + # The synthesized entry shows up under the legacy- prefix. + run /bin/bash -c "dokku storage:list-entries --format json | jq -r '.[].name' | grep '^legacy-' | head -1" + echo "output: $output" + echo "status: $status" + assert_success + + # Regression for #8557: the legacy-*.json file must be readable by the + # dokku user. The migration runs as root via the install trigger, so + # without an explicit chown the file lands as root:root and ps:rebuild + # fails with permission denied. + legacy_entry_name=$(dokku storage:list-entries --format json | jq -r '.[] | select(.name | startswith("legacy-")) | .name' | head -1) + run /bin/bash -c "stat -c '%U:%G' /var/lib/dokku/data/storage-registry/entries/${legacy_entry_name}.json" + assert_success + assert_output "dokku:dokku" + + # docker-options no longer holds the -v line on either phase. + run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-deploy" + assert_success + assert_output_not_contains "-v /tmp/legacy-mount:/legacy" + + run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-run" + assert_success + assert_output_not_contains "-v /tmp/legacy-mount:/legacy" + + # Idempotency: re-running storage:migrate is a no-op (still one + # attachment, no errors). + run /bin/bash -c "dokku storage:migrate $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku storage:list $TEST_APP --format json | jq -r '. | length'" + assert_success + assert_output "1" + + # Property marker is set after a successful migration with mounts. + run /bin/bash -c "sudo test -f /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated" + assert_success + run /bin/bash -c "sudo cat /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated" + assert_success + assert_output "true" + + # Cleanup: unmount + destroy the synthesized legacy entry. We need + # the entry name from list-entries since legacy- is content-derived. + legacy_name=$(dokku storage:list-entries --format json | jq -r '.[] | select(.name | startswith("legacy-")) | .name' | head -1) + if [[ -n "$legacy_name" ]]; then + run /bin/bash -c "dokku storage:unmount $TEST_APP $legacy_name --container-dir /legacy" + assert_success + run /bin/bash -c "dokku storage:destroy $legacy_name --force" + assert_success + fi +} + +@test "(storage) storage:ensure-directory emits deprecation warning" { + run /bin/bash -c "dokku storage:ensure-directory $TEST_APP 2>&1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Deprecated:" +} + +@test "(storage:migrate) leaves legacy-mounts-migrated unset for apps with no legacy -v lines" { + # Fresh app with no -v lines: storage:migrate must NOT write the + # legacy-mounts-migrated property. Distinguishes "never had legacy + # state" from "had legacy state, drained". + sudo rm -f "/var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated" + + run /bin/bash -c "dokku storage:migrate $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sudo test -f /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated" + assert_failure +} + +@test "(storage:migrate) converts legacy migration flag file into property" { + # Stage an upgrade-from-old-code scenario: a leftover flag file under + # data/storage-registry/migrations/. storage:migrate (which runs + # convertLegacyMigrationFlag before migrateApp) must drain it into + # the property and remove the file. + flag_dir="/var/lib/dokku/data/storage-registry/migrations" + sudo mkdir -p "$flag_dir" + sudo touch "$flag_dir/$TEST_APP" + sudo chown dokku:dokku "$flag_dir/$TEST_APP" + sudo rm -f "/var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated" + + run /bin/bash -c "dokku storage:migrate $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sudo test -f /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated" + assert_success + run /bin/bash -c "sudo cat /var/lib/dokku/config/storage/$TEST_APP/legacy-mounts-migrated" + assert_success + assert_output "true" + + run /bin/bash -c "sudo test -e $flag_dir/$TEST_APP" + assert_failure +} + +@test "(storage:report) emits new stripped JSON keys alongside legacy" { + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"build-mounts\") and has(\"storage-build-mounts\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"deploy-mounts\") and has(\"storage-deploy-mounts\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"run-mounts\") and has(\"storage-run-mounts\")'" + assert_success + assert_output "true" + + # per-attachment dotted keys are emitted in both stripped and legacy forms too + run /bin/bash -c "dokku storage:create rdmtest-rpt-keys" + assert_success + run /bin/bash -c "dokku storage:mount $TEST_APP rdmtest-rpt-keys --container-dir /data" + assert_success + + run /bin/bash -c "dokku storage:report $TEST_APP --format json | jq -r 'has(\"attachment.1.entry-name\") and has(\"storage-attachment.1.entry-name\")'" + assert_success + assert_output "true" + + run /bin/bash -c "dokku storage:unmount $TEST_APP rdmtest-rpt-keys --container-dir /data" + assert_success + run /bin/bash -c "dokku storage:destroy rdmtest-rpt-keys --force" + assert_success } diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index da08bfff6bd..fb35b626a8f 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -4,16 +4,20 @@ DOKKU_ROOT=${DOKKU_ROOT:=~dokku} DOCKER_BIN=${DOCKER_BIN:="docker"} DOKKU_LIB_ROOT=${DOKKU_LIB_PATH:="/var/lib/dokku"} +DOKKU_DOMAIN=${DOKKU_DOMAIN:="dokku.me"} PLUGIN_PATH=${PLUGIN_PATH:="$DOKKU_LIB_ROOT/plugins"} PLUGIN_AVAILABLE_PATH=${PLUGIN_AVAILABLE_PATH:="$PLUGIN_PATH/available"} PLUGIN_ENABLED_PATH=${PLUGIN_ENABLED_PATH:="$PLUGIN_PATH/enabled"} PLUGIN_CORE_PATH=${PLUGIN_CORE_PATH:="$DOKKU_LIB_ROOT/core-plugins"} PLUGIN_CORE_AVAILABLE_PATH=${PLUGIN_CORE_AVAILABLE_PATH:="$PLUGIN_CORE_PATH/available"} -CUSTOM_TEMPLATE_SSL_DOMAIN=customssltemplate.dokku.me +CUSTOM_TEMPLATE_SSL_DOMAIN="customssltemplate.${DOKKU_DOMAIN}" UUID=$(uuidgen) TEST_APP="rdmtestapp-${UUID}" TEST_NETWORK="test-network-${UUID}" SKIPPED_TEST_ERR_MSG="previous test failed! skipping remaining tests..." +TEST_PLUGIN_NAME=smoke-test-plugin +TEST_PLUGIN_GIT_REPO=https://github.com/dokku/${TEST_PLUGIN_NAME}.git +TEST_PLUGIN_LOCAL_REPO="$(mktemp -d)/$TEST_PLUGIN_NAME" # global setup() and teardown() # skips remaining tests on first failure @@ -27,6 +31,7 @@ global_setup() { global_teardown() { [[ -n "$BATS_TEST_COMPLETED" ]] || touch "${BATS_PARENT_TMPNAME}.skip" + rm -f "${BATS_PARENT_TMPNAME}.skip" || true cleanup_apps cleanup_containers } @@ -44,6 +49,15 @@ cleanup_containers() { fi } +clone_test_plugin() { + git clone "$TEST_PLUGIN_GIT_REPO" "$TEST_PLUGIN_LOCAL_REPO" +} + +remove_test_plugin() { + rm -rf "${PLUGIN_ENABLED_PATH:?}/$TEST_PLUGIN_NAME" "${PLUGIN_AVAILABLE_PATH:?}/$TEST_PLUGIN_NAME" + rm -rf "$TEST_PLUGIN_LOCAL_REPO" +} + # test functions flunk() { { @@ -57,8 +71,6 @@ flunk() { } # ShellCheck doesn't know about $status from Bats -# shellcheck disable=SC2154 -# shellcheck disable=SC2120 assert_success() { if [[ "$status" -ne 0 ]]; then flunk "command failed with exit status $status" @@ -68,8 +80,6 @@ assert_success() { } # ShellCheck doesn't know about $status from Bats -# shellcheck disable=SC2154 -# shellcheck disable=SC2120 assert_failure() { if [[ "$status" -eq 0 ]]; then flunk "expected failed exit status" @@ -97,7 +107,6 @@ assert_not_equal() { } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_output() { local expected if [[ $# -eq 0 ]]; then @@ -109,7 +118,6 @@ assert_output() { } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_not_output() { local expected if [[ $# -eq 0 ]]; then @@ -121,33 +129,38 @@ assert_not_output() { } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_output_exists() { [[ -n "$output" ]] || flunk "expected output, found none" } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_output_not_exists() { [[ -z "$output" ]] || flunk "expected no output, found some" } # ShellCheck doesn't know about $output from Bats -# shellcheck disable=SC2154 assert_output_contains() { local input="$output" local expected="$1" local count="${2:-1}" - local found=0 - until [ "${input/$expected/}" = "$input" ]; do - input="${input/$expected/}" - found=$((found + 1)) - done - assert_equal "$count" "$found" + local found + found=$(printf '%s' "$input" | { grep -F -o -- "$expected" || true; } | wc -l | tr -d ' ') + + if [[ "$count" -eq -1 ]]; then + if [[ "$found" -gt 0 ]]; then + return 0 + fi + flunk "expected at least 1 occurrence of '$expected', found $found" + fi + + [[ "$count" -eq "$found" ]] || flunk "expected $count occurrences of '$expected', found $found" +} + +assert_output_not_contains() { + assert_output_contains "$1" 0 } # ShellCheck doesn't know about $lines from Bats -# shellcheck disable=SC2154 assert_line() { if [[ "$1" -ge 0 ]] 2>/dev/null; then assert_equal "$2" "${lines[$1]}" @@ -161,7 +174,6 @@ assert_line() { } # ShellCheck doesn't know about $lines from Bats -# shellcheck disable=SC2154 assert_line_count() { declare EXPECTED="$1" local num_lines="${#lines[@]}" @@ -206,9 +218,7 @@ create_key() { } destroy_app() { - local RC="$1" - local RC=${RC:=0} - local APP="$2" + declare RC="${1:-0}" APP="$2" local TEST_APP=${APP:=$TEST_APP} dokku --force apps:destroy "$TEST_APP" return "$RC" @@ -218,11 +228,29 @@ destroy_key() { rm -f /tmp/testkey* &>/dev/null || true } -add_domain() { - dokku domains:add "$TEST_APP" "$1" +# Run a plugn trigger via `dokku plugin:trigger`, capturing $output/$status via bats `run`. +# Optional VAR=VAL pairs before are forwarded as extra env vars on the bash command line. +# Usage: run_plugn_trigger [VAR=VAL ...] +run_plugn_trigger() { + local extra_env="" + while [[ "$1" == *=* ]]; do + extra_env+=" $1" + shift + done + local TRIGGER="$1" + shift + run /bin/bash -c "${extra_env} dokku plugin:trigger $TRIGGER $* < /dev/null" +} + +# Run a single plugin's trigger script directly so other plugins' handlers do not fire. +# Captures $output/$status via bats `run`. +# Usage: run_plugin_script +run_plugin_script() { + local PLUGIN_NAME="$1" SCRIPT_NAME="$2" + shift 2 + run /bin/bash -c "PLUGIN_PATH=$PLUGIN_PATH PLUGIN_AVAILABLE_PATH=$PLUGIN_AVAILABLE_PATH PLUGIN_ENABLED_PATH=$PLUGIN_ENABLED_PATH PLUGIN_CORE_AVAILABLE_PATH=$PLUGIN_CORE_AVAILABLE_PATH DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT $PLUGIN_ENABLED_PATH/$PLUGIN_NAME/$SCRIPT_NAME $*" } -# shellcheck disable=SC2119 check_urls() { local PATTERN="$1" run /bin/bash -c "dokku urls $TEST_APP | grep -E \"${PATTERN}\"" @@ -239,6 +267,52 @@ assert_http_success() { assert_output "200" } +assert_http_localhost_response() { + local scheme="$1" domain="$2" port="${3:-80}" path="${4:-}" content="${5:-}" status_code="${6:-200}" + local retries="${HTTP_ASSERT_RETRIES:-30}" attempt=1 + while [[ "$attempt" -lt "$retries" ]]; do + run curl --connect-to "$domain:$port:localhost:$port" -kSso /dev/null -w "%{http_code}" "$scheme://$domain:$port$path" + [[ "$output" == "$status_code" ]] && break + sleep 1 + attempt=$((attempt + 1)) + done + echo "curl: curl --connect-to $domain:$port:localhost:$port -kSso /dev/null -w %{http_code} $scheme://$domain:$port$path" + echo "output: $output" + echo "status: $status" + echo "attempts: $attempt" + assert_output "$status_code" + + if [[ -n "$content" ]]; then + run curl --connect-to "$domain:$port:localhost:$port" -kSs "$scheme://$domain:$port$path" + echo "output: $output" + echo "status: $status" + assert_output "$content" + fi +} + +assert_http_localhost_response_contains() { + local scheme="$1" domain="$2" port="${3:-80}" path="${4:-}" content="${5:-}" status_code="${6:-200}" + local retries="${HTTP_ASSERT_RETRIES:-30}" attempt=1 + while [[ "$attempt" -lt "$retries" ]]; do + run curl --connect-to "$domain:$port:localhost:$port" -kSso /dev/null -w "%{http_code}" "$scheme://$domain:$port$path" + [[ "$output" == "$status_code" ]] && break + sleep 1 + attempt=$((attempt + 1)) + done + echo "curl: curl --connect-to $domain:$port:localhost:$port -kSso /dev/null -w %{http_code} $scheme://$domain:$port$path" + echo "output: $output" + echo "status: $status" + echo "attempts: $attempt" + assert_output "$status_code" + + if [[ -n "$content" ]]; then + run curl --connect-to "$domain:$port:localhost:$port" -kSs "$scheme://$domain:$port$path" + echo "output: $output" + echo "status: $status" + assert_output_contains "$content" + fi +} + assert_ssl_domain() { local domain=$1 assert_app_domain "${domain}" @@ -288,10 +362,14 @@ assert_not_external_port() { assert_url() { url="$1" - run /bin/bash -c "dokku url $TEST_APP" + run /bin/bash -c "dokku url $TEST_APP | xargs" + echo "VHOST: $(cat $DOKKU_ROOT/$TEST_APP/VHOST | xargs)" + echo "tls: $(ls $DOKKU_ROOT/$TEST_APP/tls || true)" + echo "proxy-is-enabled: $(dokku plugin:trigger proxy-is-enabled "$TEST_APP")" + echo "port-map: $(dokku ports:report "$TEST_APP" --ports-map)" + echo "url: $(dokku urls $TEST_APP)" echo "output: $output" echo "status: $status" - echo "url: ${url}" assert_output "${url}" } @@ -299,27 +377,33 @@ assert_urls() { # shellcheck disable=SC2124 urls="$@" run /bin/bash -c "dokku urls $TEST_APP | xargs" + echo "VHOST: $(cat $DOKKU_ROOT/$TEST_APP/VHOST | xargs)" + echo "tls: $(ls $DOKKU_ROOT/$TEST_APP/tls || true)" + echo "proxy-is-enabled: $(dokku plugin:trigger proxy-is-enabled "$TEST_APP")" + echo "port-map: $(dokku ports:report "$TEST_APP" --ports-map)" + echo "urls: $(dokku urls $TEST_APP)" echo "output: $output" echo "status: $status" - echo "urls:" "$urls" - assert_output "$urls" + for url in "$@"; do + assert_output_contains "$url" + done } deploy_app() { declare APP_TYPE="$1" GIT_REMOTE="$2" CUSTOM_TEMPLATE="$3" CUSTOM_PATH="$4" local APP_TYPE=${APP_TYPE:="python"} - local GIT_REMOTE=${GIT_REMOTE:="dokku@dokku.me:$TEST_APP"} + local GIT_REMOTE=${GIT_REMOTE:="dokku@${DOKKU_DOMAIN}:$TEST_APP"} local GIT_REMOTE_BRANCH=${GIT_REMOTE_BRANCH:="master"} - local TMP=${CUSTOM_TMP:=$(mktemp -d "/tmp/dokku.me.XXXXX")} + local TMP=${CUSTOM_TMP:=$(mktemp -d "/tmp/${DOKKU_DOMAIN}.XXXXX")} rmdir "$TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/$APP_TYPE" "$TMP" - # shellcheck disable=SC2086 [[ -n "$CUSTOM_TEMPLATE" ]] && $CUSTOM_TEMPLATE $TEST_APP $TMP/$CUSTOM_PATH pushd "$TMP" &>/dev/null || exit 1 [[ -z "$CUSTOM_TMP" ]] && trap 'popd &>/dev/null || true; rm -rf "$TMP"' RETURN INT TERM + git config --global init.defaultBranch master git init git config user.email "robot@example.com" git config user.name "Test Robot" @@ -332,23 +416,13 @@ deploy_app() { git push target "master:${GIT_REMOTE_BRANCH}" || destroy_app $? } -setup_client_repo() { - local TMP - TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") - rmdir "$TMP" && cp -r "${BATS_TEST_DIRNAME}/../../tests/apps/nodejs-express" "$TMP" - cd "$TMP" || exit 1 - git init - git config user.email "robot@example.com" - git config user.name "Test Robot" - - [[ -f gitignore ]] && mv gitignore .gitignore - git add . - git commit -m 'initial commit' -} - setup_test_tls() { local TLS_TYPE="$1" local TLS="/home/dokku/$TEST_APP/tls" + + if ! dokku apps:exists "$TEST_APP" &>/dev/null; then + create_app "$TEST_APP" + fi mkdir -p "$TLS" case "$TLS_TYPE" in @@ -366,6 +440,11 @@ setup_test_tls() { sudo chown -R dokku:dokku "${TLS}/.." } +teardown_test_tls() { + local TLS="/home/dokku/$TEST_APP/tls" + rm -rf "$TLS" +} + custom_ssl_nginx_template() { local APP="$1" local APP_REPO_DIR="$2" @@ -388,15 +467,14 @@ server { } {{ else if eq \$scheme "https"}} server { - listen [::]:{{ $.PROXY_SSL_PORT }} ssl spdy; - listen {{ $.PROXY_SSL_PORT }} ssl spdy; + listen [::]:{{ $.PROXY_SSL_PORT }} ssl; + listen {{ $.PROXY_SSL_PORT }} ssl; {{ if $.SSL_SERVER_NAME }}server_name {{ $.SSL_SERVER_NAME }} $CUSTOM_TEMPLATE_SSL_DOMAIN; {{ end }} {{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }} $CUSTOM_TEMPLATE_SSL_DOMAIN; {{ end }} ssl_certificate {{ $.APP_SSL_PATH }}/server.crt; ssl_certificate_key {{ $.APP_SSL_PATH }}/server.key; keepalive_timeout 70; - add_header Alternate-Protocol {{ \$listen_port }}:npn-spdy/2; location / { proxy_pass http://{{ $.APP }}-{{ \$upstream_port }}; proxy_http_version 1.1; @@ -442,7 +520,7 @@ custom_nginx_template() { server { listen [::]:{{ \$listen_port }}; listen {{ \$listen_port }}; - server_name {{ $.NOSSL_SERVER_NAME }} customtemplate.dokku.me; + server_name {{ $.NOSSL_SERVER_NAME }} customtemplate.${DOKKU_DOMAIN}; location / { proxy_pass http://{{ $.APP }}-{{ \$upstream_port }}; @@ -511,74 +589,166 @@ add_release_command() { echo "release: touch /app/release.test" >>"$APP_REPO_DIR/Procfile" } -add_postdeploy_command() { +move_dockerfile_into_place() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + mv "$APP_REPO_DIR/alt.Dockerfile" "$APP_REPO_DIR/Dockerfile" +} + +move_expose_dockerfile_into_place() { local APP="$1" local APP_REPO_DIR="$2" [[ -z "$APP" ]] && local APP="$TEST_APP" - touch "$APP_REPO_DIR/app.json" - contents=$(jq '.scripts.postdeploy = "touch /app/heroku-postdeploy.test"' "$APP_REPO_DIR/app.json") - echo "${contents}" >"$APP_REPO_DIR/app.json" + cat "$APP_REPO_DIR/expose.Dockerfile" + mv "$APP_REPO_DIR/expose.Dockerfile" "$APP_REPO_DIR/Dockerfile" } add_requirements_txt() { local APP="$1" local APP_REPO_DIR="$2" [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + echo "flask" >>"$APP_REPO_DIR/requirements.txt" } -build_nginx_config() { - # simulate nginx post-deploy - dokku domains:setup "$TEST_APP" - dokku proxy:build-config "$TEST_APP" +add_requirements_txt_cnb() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + + mv "$APP_REPO_DIR/app-cnb.json" "$APP_REPO_DIR/app.json" + add_requirements_txt "$APP" "$APP_REPO_DIR" } create_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" NETWORK=$(docker network ls -q -f name="$NETWORK_NAME") [[ -z "$NETWORK" ]] && docker network create "$NETWORK_NAME" } attach_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" NETWORK=$(docker network ls -q -f name="$NETWORK_NAME") [[ -n "$NETWORK" ]] && docker network connect "$NETWORK_NAME" "${TEST_APP}.web.1" } create_attach_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" create_network "$NETWORK_NAME" attach_network "$NETWORK_NAME" } delete_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" NETWORK=$(docker network ls -q -f name="$NETWORK_NAME") [[ -n "$NETWORK" ]] && docker network rm "$NETWORK_NAME" } detach_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" NETWORK=$(docker network ls -q -f name="$NETWORK_NAME") [[ -z "$NETWORK" ]] && docker network disconnect "$NETWORK_NAME" "${TEST_APP}.web.1" } detach_delete_network() { - local NETWORK_NAME="$1:=$TEST_NETWORK" + local NETWORK_NAME="${1:-$TEST_NETWORK}" detach_network "$NETWORK_NAME" delete_network "$NETWORK_NAME" } +convert_to_dockerfile() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + mv "$APP_REPO_DIR/dockerfile.Dockerfile" "$APP_REPO_DIR/Dockerfile" +} + install_pack() { - if ! command -v "pack" &>/dev/null; then - add-apt-repository --yes ppa:cncf-buildpacks/pack-cli - apt-get update - apt-get --yes install pack-cli + if [[ ! -x /usr/bin/pack ]]; then + local DEPS_FILE="${BATS_TEST_DIRNAME}/../../contrib/dependencies.json" + local PACK_URL + PACK_URL="$(jq -r --arg arch "$(dpkg --print-architecture)" '.dependencies[] | select(.name == "pack") | .urls[$arch]' "$DEPS_FILE")" + curl -fsSL "$PACK_URL" | tar -C /usr/bin --no-same-owner -xzf - pack + chmod +x /usr/bin/pack fi } + +install_nixpacks() { + if ! command -v "nixpacks" &>/dev/null; then + curl -sSL https://nixpacks.com/install.sh | FORCE=1 bash + fi +} + +install_railpack() { + if ! command -v "railpack" &>/dev/null; then + curl -sSL https://railpack.com/install.sh | FORCE=1 bash + fi +} + +install_k3s() { + run /bin/bash -c "dokku proxy:set --global k3s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:set --global server hub.docker.com" + echo "output: $output" + echo "status: $status" + assert_success + + local test_basename + test_basename="$(basename "$BATS_TEST_FILENAME" .bats)" + + run /bin/bash -c "dokku registry:set --global image-repo-template '$DOCKERHUB_USERNAME/$test_basename-{{ .AppName }}'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:set --global push-on-release true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler:set --global selected k3s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku registry:login docker.io $DOCKERHUB_USERNAME $DOCKERHUB_TOKEN" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "Login Succeeded" + + INGRESS_CLASS="${INGRESS_CLASS:-traefik}" + local args="--ingress-class $INGRESS_CLASS" + if [[ "$TAINT_SCHEDULING" == "true" ]]; then + args="$args --taint-scheduling" + fi + + if [[ -n "$CI_SERVER_IP" ]]; then + args="$args --server-ip $CI_SERVER_IP" + fi + + run /bin/bash -c "dokku scheduler-k3s:initialize ${args}" + echo "output: $output" + echo "status: $status" + assert_success +} + +uninstall_k3s() { + run /bin/bash -c "dokku scheduler-k3s:uninstall" + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/tests/unit/traefik-vhosts.bats b/tests/unit/traefik-vhosts.bats new file mode 100644 index 00000000000..b1d6a6e9f92 --- /dev/null +++ b/tests/unit/traefik-vhosts.bats @@ -0,0 +1,72 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + create_app +} + +teardown() { + destroy_app + dokku traefik:set --global challenge-mode >/dev/null 2>&1 || true + dokku traefik:set --global dns-provider-test_key >/dev/null 2>&1 || true +} + +@test "(traefik:report) info-flag works before deploy" { + run /bin/bash -c "dokku traefik:set --global challenge-mode" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "tls" + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-invalid-flag" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "Invalid flag passed" +} + +@test "(traefik:report) --format json" { + run /bin/bash -c "dokku traefik:report --global --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"computed-api-enabled\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" + + run /bin/bash -c "dokku traefik:report --global" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "global traefik information" +} + +@test "(traefik:report) dns-provider values are masked unless queried" { + run /bin/bash -c "dokku traefik:set --global dns-provider-test_key secretvalue" + assert_success + + run /bin/bash -c "dokku traefik:report --global | grep test_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "*******" + + run /bin/bash -c "dokku traefik:report --global --traefik-dns-provider-test_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secretvalue" + + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"dns-provider-test_key\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secretvalue" +} diff --git a/tests/unit/traefik.bats b/tests/unit/traefik.bats new file mode 100644 index 00000000000..839ba1fb7f0 --- /dev/null +++ b/tests/unit/traefik.bats @@ -0,0 +1,890 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + dokku nginx:stop + dokku traefik:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory + dokku traefik:set --global letsencrypt-email + dokku traefik:set --global api-enabled + dokku traefik:set --global api-entry-point + dokku traefik:set --global api-entry-point-address + dokku traefik:set --global challenge-mode + dokku traefik:set --global dns-provider + dokku traefik:start + create_app +} + +teardown() { + global_teardown + destroy_app + dokku traefik:stop + dokku nginx:start +} + +@test "(traefik:report) --global global vs computed log-level" { + run /bin/bash -c "dokku traefik:report --global --traefik-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku traefik:report --global --traefik-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" + + run /bin/bash -c "dokku traefik:set --global log-level DEBUG" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report --global --traefik-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "DEBUG" + + run /bin/bash -c "dokku traefik:report --global --traefik-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "DEBUG" + + run /bin/bash -c "dokku traefik:set --global log-level" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report --global --traefik-global-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku traefik:report --global --traefik-computed-log-level" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "ERROR" +} + +@test "(traefik:report) --global raw and computed keys in --format json" { + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"global-api-vhost\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"computed-api-vhost\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "traefik.dokku.me" + + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"global-dashboard-enabled\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "" + + run /bin/bash -c "dokku traefik:report --global --format json | jq -r '.\"computed-dashboard-enabled\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "false" +} + +@test "(traefik) global-only keys" { + for key in api-enabled api-entry-point api-entry-point-address api-vhost basic-auth-username basic-auth-password challenge-mode dashboard-enabled dns-provider image letsencrypt-email letsencrypt-server log-level http-entry-point https-entry-point; do + run /bin/bash -c "dokku traefik:set $TEST_APP $key somevalue" + echo "key: $key" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" + done + + run /bin/bash -c "dokku traefik:set $TEST_APP dns-provider-CF_API_TOKEN somevalue" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) traefik:help" { + run /bin/bash -c "dokku traefik" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the traefik proxy integration" + help_output="$output" + + run /bin/bash -c "dokku traefik:help" + echo "output: $output" + echo "status: $status" + assert_output_contains "Manage the traefik proxy integration" + assert_output "$help_output" +} + +@test "(traefik) single domain" { + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "curl --silent $(dokku url $TEST_APP)" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "python/http.server" +} + +@test "(traefik) multiple domains" { + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add $TEST_APP $TEST_APP-2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP convert_to_dockerfile + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "curl --silent $TEST_APP.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "python/http.server" + + run /bin/bash -c "curl --silent $TEST_APP-2.${DOKKU_DOMAIN}" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "python/http.server" +} + +@test "(traefik) traefik:set api" { + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect traefik-traefik-1 --format '{{ index .Config.Labels \"traefik.http.routers.api.rule\" }}'" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + assert_success + + run /bin/bash -c "dokku traefik:set --global api-enabled false" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect traefik-traefik-1 --format '{{ index .Config.Labels \"traefik.http.routers.api.rule\" }}'" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + assert_success + + run /bin/bash -c "dokku traefik:set --global api-enabled true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect traefik-traefik-1 --format '{{ index .Config.Labels \"traefik.http.routers.api.rule\" }}'" + echo "output: $output" + echo "status: $status" + assert_output "Host(\`traefik.$DOKKU_DOMAIN\`)" + assert_success +} + +@test "(traefik) ssl" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.services.$TEST_APP-web-http.loadbalancer.server.port\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5000" + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.services.$TEST_APP-web-https.loadbalancer.server.port\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku traefik:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:stop" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:start" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:inspect $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.services.$TEST_APP-web-http.loadbalancer.server.port\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5000" + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.services.$TEST_APP-web-https.loadbalancer.server.port\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5000" + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map" + echo "output: $output" + echo "status: $status" + assert_output_not_exists + + run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected" + echo "output: $output" + echo "status: $status" + assert_output "http:80:5000 https:443:5000" +} + +@test "(traefik) show-config without auth set" { + run /bin/bash -c "dokku traefik:set --global basic-auth-username \"\"" + run /bin/bash -c "dokku traefik:set --global basic-auth-password \"\"" + + run /bin/bash -c "dokku traefik:show-config" + echo "output: $output" + echo "status: $status" + assert_success + refute_line ' - "traefik.http.routers.api.middlewares=auth"' +} + +@test "(traefik) show-config with auth set" { + run /bin/bash -c "dokku traefik:set --global basic-auth-username test-username" + run /bin/bash -c "dokku traefik:set --global basic-auth-password test-password" + + run /bin/bash -c "dokku traefik:show-config" + echo "output: $output" + echo "status: $status" + assert_success + assert_line ' - "traefik.http.routers.api.middlewares=auth"' +} + +@test "(traefik) change traefik entry point http" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global http-entry-point web" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.routers.$TEST_APP-web-http.entrypoints\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "web" + +} + +@test "(traefik) change traefik entry point https" { + run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global https-entry-point websecure" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.routers.$TEST_APP-web-https.entrypoints\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "websecure" +} + +@test "(traefik) label management" { + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:labels:add $TEST_APP traefik.directive value" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:labels:show $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "traefik.directive=value" + + run /bin/bash -c "dokku traefik:labels:show $TEST_APP traefik.directive" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "value" + + run /bin/bash -c "dokku traefik:labels:show $TEST_APP traefik.directive2" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.directive\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "value" + + run /bin/bash -c "dokku traefik:labels:remove $TEST_APP traefik.directive" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:labels:show $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "traefik.directive=value" + + run /bin/bash -c "dokku traefik:labels:show $TEST_APP traefik.directive" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku ps:rebuild $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.directive\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +@test "(traefik) [dns-01] challenge-mode property" { + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "tls" + + run /bin/bash -c "dokku traefik:set --global challenge-mode dns" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "dns" + + run /bin/bash -c "dokku traefik:set --global challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-challenge-mode" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "tls" +} + +@test "(traefik) [dns-01] dns-provider property" { + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-dns-provider" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku traefik:set --global dns-provider cloudflare" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-dns-provider" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "cloudflare" + + run /bin/bash -c "dokku traefik:set --global dns-provider" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-dns-provider" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +@test "(traefik) [dns-01] dns-provider-* environment variables" { + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key secret-key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "dns provider cf_api_email" + assert_output_contains "dns provider cf_api_key" + assert_output_contains "*******" 2 + assert_output_not_contains "test@example.com" + assert_output_not_contains "secret-key" + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-dns-provider-cf_api_email" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "test@example.com" + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-dns-provider-cf_api_key" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secret-key" + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_contains "dns provider cf_api_email" + assert_output_not_contains "dns provider cf_api_key" +} + +@test "(traefik) [dns-01] dns-provider-* can only be set globally" { + run /bin/bash -c "dokku traefik:set $TEST_APP dns-provider-cf_api_email test@example.com" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) [dns-01] challenge-mode can only be set globally" { + run /bin/bash -c "dokku traefik:set $TEST_APP challenge-mode dns" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) [dns-01] dns-provider can only be set globally" { + run /bin/bash -c "dokku traefik:set $TEST_APP dns-provider cloudflare" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) [dns-01] report json format shows unmasked dns-provider-* values" { + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key secret-key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --format json | jq -r '.\"dns-provider-cf_api_email\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "test@example.com" + + run /bin/bash -c "dokku traefik:report $TEST_APP --format json | jq -r '.\"dns-provider-cf_api_key\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "secret-key" + + # Verify stdout format still shows masked values + run /bin/bash -c "dokku traefik:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "*******" 2 + assert_output_not_contains "test@example.com" + assert_output_not_contains "secret-key" + + # Cleanup + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email" + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key" +} + +@test "(traefik) [dns-01] show-config with tls challenge" { + run /bin/bash -c "dokku traefik:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(. == \"--certificatesresolvers.leresolver.acme.tlschallenge=true\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--certificatesresolvers.leresolver.acme.tlschallenge=true" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(contains(\"dnschallenge\"))'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +@test "(traefik) api-entry-point property" { + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-api-entry-point" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + run /bin/bash -c "dokku traefik:set --global api-entry-point private" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-api-entry-point" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "private" + + run /bin/bash -c "dokku traefik:set --global api-entry-point" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:report $TEST_APP --traefik-computed-api-entry-point" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +@test "(traefik) api-entry-point can only be set globally" { + run /bin/bash -c "dokku traefik:set $TEST_APP api-entry-point private" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) api-entry-point-address can only be set globally" { + run /bin/bash -c "dokku traefik:set $TEST_APP api-entry-point-address :8080" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" +} + +@test "(traefik) show-config with api-entry-point" { + run /bin/bash -c "dokku traefik:set --global api-enabled true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global api-entry-point private" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global api-entry-point-address :8080" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(. == \"--entrypoints.private.address=:8080\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--entrypoints.private.address=:8080" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.labels[] | select(. == \"traefik.http.routers.api.entrypoints=private\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "traefik.http.routers.api.entrypoints=private" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.ports[] | select(. == \":8080:8080\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output ":8080:8080" + + # Cleanup + run /bin/bash -c "dokku traefik:set --global api-enabled" + run /bin/bash -c "dokku traefik:set --global api-entry-point" + run /bin/bash -c "dokku traefik:set --global api-entry-point-address" +} + +@test "(traefik) show-config with api-entry-point does not set tls cert resolver" { + run /bin/bash -c "dokku traefik:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global api-enabled true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global api-entry-point private" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.labels[] | select(contains(\"certresolver\"))'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + # Cleanup + run /bin/bash -c "dokku traefik:set --global letsencrypt-email" + run /bin/bash -c "dokku traefik:set --global api-enabled" + run /bin/bash -c "dokku traefik:set --global api-entry-point" +} + +@test "(traefik) [dns-01] show-config with dns challenge" { + run /bin/bash -c "dokku traefik:set --global letsencrypt-email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global challenge-mode dns" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider cloudflare" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email test@example.com" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key secret-key" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(. == \"--certificatesresolvers.leresolver.acme.dnschallenge=true\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--certificatesresolvers.leresolver.acme.dnschallenge=true" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(. == \"--certificatesresolvers.leresolver.acme.dnschallenge.provider=cloudflare\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "--certificatesresolvers.leresolver.acme.dnschallenge.provider=cloudflare" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.environment[] | select(. == \"CF_API_EMAIL=test@example.com\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "CF_API_EMAIL=test@example.com" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.environment[] | select(. == \"CF_API_KEY=secret-key\")'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "CF_API_KEY=secret-key" + + run /bin/bash -c "dokku traefik:show-config | yq -r '.services.traefik.command[] | select(contains(\"tlschallenge\"))'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists + + # Cleanup + run /bin/bash -c "dokku traefik:set --global challenge-mode" + run /bin/bash -c "dokku traefik:set --global dns-provider" + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_email" + run /bin/bash -c "dokku traefik:set --global dns-provider-cf_api_key" +} + +@test "(traefik) healthcheck labels from app.json" { + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP setup_traefik_healthcheck + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.services.$TEST_APP-web-http.loadbalancer.healthcheck.path\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "/" + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.services.$TEST_APP-web-http.loadbalancer.healthcheck.timeout\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5s" + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.services.$TEST_APP-web-http.loadbalancer.healthcheck.interval\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "2s" +} + +@test "(traefik) no healthcheck labels without readiness check" { + run /bin/bash -c "dokku proxy:set $TEST_APP traefik" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "docker inspect $TEST_APP.web.1 --format '{{ index .Config.Labels \"traefik.http.services.$TEST_APP-web-http.loadbalancer.healthcheck.path\" }}'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_not_exists +} + +setup_traefik_healthcheck() { + local APP="$1" + local APP_REPO_DIR="$2" + [[ -z "$APP" ]] && local APP="$TEST_APP" + APP_REPO_DIR="$(realpath "$APP_REPO_DIR")" + + mv "$APP_REPO_DIR/app-traefik.json" "$APP_REPO_DIR/app.json" +}