diff --git a/Dockerfile b/Dockerfile
index f598c523099..dead7581088 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -33,6 +33,11 @@ RUN mkdir -p /etc/apt/keyrings \
&& 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 \
diff --git a/docker/etc/sudoers.d/dokku-docker b/docker/etc/sudoers.d/dokku-docker
index bebd885389b..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-container-healthchecker,/usr/bin/docker-image-labeler,/usr/bin/lambda-builder,/usr/bin/nixpacks,/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/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/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/deployment/builders/railpack.md b/docs/deployment/builders/railpack.md
new file mode 100644
index 00000000000..dc0d634fb70
--- /dev/null
+++ b/docs/deployment/builders/railpack.md
@@ -0,0 +1,172 @@
+# Railpack
+
+> [!IMPORTANT]
+> New as of 0.32.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
+
+Cache is enabled by default, but can be disabled by setting the `no-cache` property to `true`:
+
+```shell
+dokku builder-railpack:set node-js-app no-cache true
+```
+
+The default value may be set by passing an empty value for the option:
+
+```shell
+dokku builder-railpack:set node-js-app no-cache
+```
+
+The `no-cache` property can also be set globally. The global default is `false`, and the global value is used when no app-specific value is set.
+
+```shell
+dokku builder-railpack:set --global no-cache true
+```
+
+The default value may be set by passing an empty value for the option.
+
+```shell
+dokku builder-railpack:set --global 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: railpack.json
+ Builder-railpack railpackjson path: railpack2.json
+ Builder-railpack computed no cache: true
+ Builder-railpack global no cache: false
+ Builder-railpack no cache: true
+=====> python-sample builder-railpack information
+ Builder-railpack computed railpackjson path: railpack.json
+ Builder-railpack global railpackjson path: railpack.json
+ Builder-railpack railpackjson path:
+ Builder-railpack computed no cache: false
+ Builder-railpack global no cache: false
+ Builder-railpack no cache:
+=====> ruby-sample builder-railpack information
+ Builder-railpack computed railpackjson path: railpack.json
+ Builder-railpack global railpackjson path: railpack.json
+ Builder-railpack railpackjson path:
+ Builder-railpack computed no cache: false
+ Builder-railpack global no cache: false
+ Builder-railpack no cache:
+```
+
+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: railpack.json
+ Builder-railpack railpackjson path: railpack2.json
+ Builder-railpack computed no cache: true
+ Builder-railpack global no cache: false
+ Builder-railpack no cache: true
+```
+
+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-no-cache
+```
+
+```
+true
+```
diff --git a/docs/template.html b/docs/template.html
index d81802c40dd..9e26b37590f 100644
--- a/docs/template.html
+++ b/docs/template.html
@@ -150,6 +150,7 @@
Lambda Builder
Nixpacks Builder
Null Builder
+ Railpack Builder
Deployment Methods
Git Deployment
diff --git a/plugins/builder-railpack/builder-build b/plugins/builder-railpack/builder-build
new file mode 100755
index 00000000000..e0716522d6e
--- /dev/null
+++ b/plugins/builder-railpack/builder-build
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
+source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions"
+source "$PLUGIN_AVAILABLE_PATH/config/functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+trigger-builder-railpack-builder-build() {
+ declare desc="builder-railpack builder-build plugin trigger"
+ declare trigger="builder-build"
+ declare BUILDER_TYPE="$1" APP="$2" SOURCECODE_WORK_DIR="$3"
+
+ if [[ "$BUILDER_TYPE" != "railpack" ]]; then
+ return
+ fi
+
+ dokku_log_info1 "Building $APP from railpack"
+
+ if ! command -v "railpack" &>/dev/null; then
+ dokku_log_fail "Missing railpack, install it"
+ fi
+
+ local IMAGE=$(get_app_image_name "$APP")
+ local DOCKER_BUILD_LABEL_ARGS=("--label=dokku" "--label=org.label-schema.schema-version=1.0" "--label=org.label-schema.vendor=dokku" "--label=com.dokku.image-stage=build" "--label=com.dokku.builder-type=railpack" "--label=com.dokku.app-name=$APP")
+
+ pushd "$SOURCECODE_WORK_DIR" &>/dev/null
+
+ plugn trigger pre-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR"
+
+ no_cache="$(fn-builder-railpack-computed-no-cache "$APP")"
+ RAILPACK_ARGS=""
+ if [[ "$no_cache" == "true" ]]; then
+ RAILPACK_ARGS="$RAILPACK_ARGS --no-cache"
+ fi
+
+ local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$BUILDER_TYPE")
+ DOCKER_ARGS+=$(: | plugn trigger docker-args-process-build "$APP" "$BUILDER_TYPE")
+
+ # strip --link, --volume and -v args from DOCKER_ARGS
+ local DOCKER_ARGS=$(sed -e "s/^--link=[[:graph:]]\+[[:blank:]]\?//g" -e "s/^--link[[:blank:]]\?[[:graph:]]\+[[:blank:]]\?//g" -e "s/^--volume=[[:graph:]]\+[[:blank:]]\?//g" -e "s/^--volume[[:blank:]]\?[[:graph:]]\+[[:blank:]]\?//g" -e "s/^-v[[:blank:]]\?[[:graph:]]\+[[:blank:]]\?//g" <<<"$DOCKER_ARGS")
+ declare -a ARG_ARRAY
+ eval "ARG_ARRAY=($DOCKER_ARGS)"
+
+ eval "$(config_export app "$APP" --merged)"
+
+ if [[ -f "$SOURCECODE_WORK_DIR/Procfile" ]]; then
+ if procfile-util exists --process-type release; then
+ procfile-util delete --process-type release
+ fi
+ fi
+
+ # shellcheck disable=SC2086
+ if ! railpack build "${ARG_ARRAY[@]}" $RAILPACK_ARGS --name "$IMAGE-build" "$SOURCECODE_WORK_DIR"; then
+ dokku_log_warn "Failure building image"
+ return 1
+ fi
+
+ if ! suppress_output "$DOCKER_BIN" image build -f "$PLUGIN_AVAILABLE_PATH/builder-railpack/dockerfiles/builder-build.Dockerfile" --build-arg APP_IMAGE="$IMAGE-build" "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -t "$IMAGE" "$SOURCECODE_WORK_DIR"; then
+ "$DOCKER_BIN" image remove "$IMAGE-build"
+ dokku_log_warn "Failure injecting docker labels and custom entrypoint on image"
+ return 1
+ fi
+
+ "$DOCKER_BIN" image remove "$IMAGE-build"
+
+ plugn trigger post-build "$BUILDER_TYPE" "$APP" "$SOURCECODE_WORK_DIR"
+ popd &>/dev/null || pushd "/tmp" >/dev/null
+}
+
+trigger-builder-railpack-builder-build "$@"
diff --git a/plugins/builder-railpack/builder-detect b/plugins/builder-railpack/builder-detect
new file mode 100755
index 00000000000..1ba0b89baf6
--- /dev/null
+++ b/plugins/builder-railpack/builder-detect
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+source "$PLUGIN_AVAILABLE_PATH/config/functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+trigger-builder-railpack-builder-detect() {
+ declare desc="builder-railpack builder-detect plugin trigger"
+ declare trigger="builder-detect"
+ declare APP="$1" SOURCECODE_WORK_DIR="$2"
+
+ if [[ -f "$SOURCECODE_WORK_DIR/railpack.json" ]]; then
+ echo "railpack"
+ return
+ fi
+}
+
+trigger-builder-railpack-builder-detect "$@"
diff --git a/plugins/builder-railpack/builder-release b/plugins/builder-railpack/builder-release
new file mode 100755
index 00000000000..7c3650421b7
--- /dev/null
+++ b/plugins/builder-railpack/builder-release
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+trigger-builder-railpack-builder-release() {
+ declare desc="builder-railpack builder-release plugin trigger"
+ declare trigger="builder-release"
+ declare BUILDER_TYPE="$1" APP="$2" IMAGE_TAG="$3"
+
+ if [[ "$BUILDER_TYPE" != "railpack" ]]; then
+ return
+ fi
+
+ local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG")
+ plugn trigger pre-release-builder "$BUILDER_TYPE" "$APP" "$IMAGE"
+
+ TMP_WORK_DIR="$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")"
+ trap "rm -rf '$TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT
+
+ local DOCKER_BUILD_LABEL_ARGS="--label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=com.dokku.app-name=$APP --label=com.dokku.image-stage=release --label=dokku"
+ if ! suppress_output "$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS -f "$PLUGIN_AVAILABLE_PATH/builder-railpack/dockerfiles/builder-release.Dockerfile" --build-arg APP_IMAGE="$IMAGE" -t "$IMAGE" "$TMP_WORK_DIR"; then
+ dokku_log_warn "Failure injecting docker labels on image"
+ return 1
+ fi
+
+ plugn trigger post-release-builder "$BUILDER_TYPE" "$APP" "$IMAGE"
+}
+
+trigger-builder-railpack-builder-release "$@"
diff --git a/plugins/builder-railpack/commands b/plugins/builder-railpack/commands
new file mode 100755
index 00000000000..db753d31107
--- /dev/null
+++ b/plugins/builder-railpack/commands
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+source "$PLUGIN_AVAILABLE_PATH/builder-railpack/help-functions"
+
+case "$1" in
+ help | builder-railpack:help)
+ cmd-builder-railpack-help "$@"
+ ;;
+
+ *)
+ exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
+ ;;
+
+esac
diff --git a/plugins/builder-railpack/core-post-extract b/plugins/builder-railpack/core-post-extract
new file mode 100755
index 00000000000..ad863c076a7
--- /dev/null
+++ b/plugins/builder-railpack/core-post-extract
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
+source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+trigger-builder-railpack-core-post-extract() {
+ declare desc="builder-railpack post-extract plugin trigger"
+ declare trigger="post-extract"
+ declare APP="$1" SOURCECODE_WORK_DIR="$2"
+ local NEW_RAILPACK_JSON="$(fn-builder-railpack-computed-railpackjson-path "$APP")"
+
+ pushd "$TMP_WORK_DIR" >/dev/null
+
+ if [[ -z "$NEW_RAILPACK_JSON" ]]; then
+ return
+ fi
+
+ if [[ ! -f "$NEW_RAILPACK_JSON" ]]; then
+ rm -f railpack.json
+ return
+ fi
+
+ if [[ "$NEW_RAILPACK_JSON" != "railpack.json" ]]; then
+ mv "$NEW_RAILPACK_JSON" railpack.json
+ fi
+ popd &>/dev/null || pushd "/tmp" >/dev/null
+}
+
+trigger-builder-railpack-core-post-extract "$@"
diff --git a/plugins/builder-railpack/dockerfiles/builder-build.Dockerfile b/plugins/builder-railpack/dockerfiles/builder-build.Dockerfile
new file mode 100644
index 00000000000..185c7e950ab
--- /dev/null
+++ b/plugins/builder-railpack/dockerfiles/builder-build.Dockerfile
@@ -0,0 +1,4 @@
+ARG APP_IMAGE
+FROM $APP_IMAGE
+
+ENTRYPOINT []
diff --git a/plugins/builder-railpack/dockerfiles/builder-release.Dockerfile b/plugins/builder-railpack/dockerfiles/builder-release.Dockerfile
new file mode 100644
index 00000000000..cca256f21d5
--- /dev/null
+++ b/plugins/builder-railpack/dockerfiles/builder-release.Dockerfile
@@ -0,0 +1,2 @@
+ARG APP_IMAGE
+FROM $APP_IMAGE
diff --git a/plugins/builder-railpack/help-functions b/plugins/builder-railpack/help-functions
new file mode 100755
index 00000000000..ef1811401bd
--- /dev/null
+++ b/plugins/builder-railpack/help-functions
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+cmd-builder-railpack-help() {
+ declare desc="help command"
+ declare CMD="$1"
+ local plugin_name="builder-railpack"
+ local plugin_description="Manage the railpack builder integration for an app"
+
+ if [[ "$CMD" == "${plugin_name}:help" ]]; then
+ echo -e "Usage: dokku ${plugin_name}[:COMMAND]"
+ echo ''
+ echo "$plugin_description"
+ echo ''
+ echo 'Additional commands:'
+ fn-help-content | sort | column -c2 -t -s,
+ elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then
+ fn-help-content
+ else
+ cat <] [], Displays a builder-railpack report for one or more apps
+ builder-railpack:set (), Set or clear a builder-railpack property for an app
+help_content
+}
diff --git a/plugins/builder-railpack/install b/plugins/builder-railpack/install
new file mode 100755
index 00000000000..283188a3319
--- /dev/null
+++ b/plugins/builder-railpack/install
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+trigger-builder-railpack-install() {
+ declare desc="installs the builder-railpack plugin"
+ declare trigger="install"
+
+ fn-plugin-property-setup "builder-railpack"
+
+ mkdir -p "${DOKKU_LIB_ROOT}/data/builder-railpack"
+ chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/builder-railpack"
+}
+
+trigger-builder-railpack-install "$@"
diff --git a/plugins/builder-railpack/internal-functions b/plugins/builder-railpack/internal-functions
new file mode 100755
index 00000000000..a405b2ed072
--- /dev/null
+++ b/plugins/builder-railpack/internal-functions
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+cmd-builder-railpack-report() {
+ declare desc="displays a builder-railpack report for one or more apps"
+ declare cmd="builder-railpack:report"
+ [[ "$1" == "$cmd" ]] && shift 1
+ declare APP="$1" INFO_FLAG="$2"
+
+ if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
+ INFO_FLAG="$APP"
+ APP=""
+ fi
+
+ if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
+ INFO_FLAG="true"
+ fi
+
+ if [[ -z "$APP" ]]; then
+ for app in $(dokku_apps); do
+ cmd-builder-railpack-report-single "$app" "$INFO_FLAG" | tee || true
+ done
+ else
+ cmd-builder-railpack-report-single "$APP" "$INFO_FLAG"
+ fi
+}
+
+cmd-builder-railpack-report-single() {
+ declare APP="$1" INFO_FLAG="$2"
+ if [[ "$INFO_FLAG" == "true" ]]; then
+ INFO_FLAG=""
+ fi
+ verify_app_name "$APP"
+ local flag_map=(
+ "--builder-railpack-computed-railpackjson-path: $(fn-builder-railpack-computed-railpackjson-path "$APP")"
+ "--builder-railpack-global-railpackjson-path: $(fn-builder-railpack-global-railpackjson-path "$APP")"
+ "--builder-railpack-railpackjson-path: $(fn-builder-railpack-railpackjson-path "$APP")"
+ "--builder-railpack-computed-no-cache: $(fn-builder-railpack-computed-no-cache "$APP")"
+ "--builder-railpack-global-no-cache: $(fn-builder-railpack-global-no-cache "$APP")"
+ "--builder-railpack-no-cache: $(fn-builder-railpack-no-cache "$APP")"
+ )
+
+ if [[ -z "$INFO_FLAG" ]]; then
+ dokku_log_info2_quiet "${APP} builder-railpack information"
+ for flag in "${flag_map[@]}"; do
+ key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')"
+ dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")"
+ done
+ else
+ local match=false
+ local value_exists=false
+ for flag in "${flag_map[@]}"; do
+ valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)"
+ if [[ "$flag" == "${INFO_FLAG}:"* ]]; then
+ value=${flag#*: }
+ size="${#value}"
+ if [[ "$size" -ne 0 ]]; then
+ echo "$value" && match=true && value_exists=true
+ else
+ match=true
+ fi
+ fi
+ done
+ [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
+ [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed"
+ fi
+}
+
+fn-builder-railpack-computed-railpackjson-path() {
+ declare APP="$1"
+
+ file="$(fn-builder-railpack-railpackjson-path "$APP")"
+ if [[ "$file" == "" ]]; then
+ file="$(fn-builder-railpack-global-railpackjson-path "$APP")"
+ fi
+
+ echo "$file"
+}
+
+fn-builder-railpack-global-railpackjson-path() {
+ declare APP="$1"
+
+ fn-plugin-property-get "builder-railpack" "--global" "railpackjson-path" "railpack.json"
+}
+
+fn-builder-railpack-railpackjson-path() {
+ declare APP="$1"
+
+ fn-plugin-property-get "builder-railpack" "$APP" "railpackjson-path" ""
+}
+
+fn-builder-railpack-computed-no-cache() {
+ declare APP="$1"
+
+ file="$(fn-builder-railpack-no-cache "$APP")"
+ if [[ "$file" == "" ]]; then
+ file="$(fn-builder-railpack-global-no-cache "$APP")"
+ fi
+
+ echo "$file"
+}
+
+fn-builder-railpack-global-no-cache() {
+ declare APP="$1"
+
+ fn-plugin-property-get "builder-railpack" "--global" "no-cache" "false"
+}
+
+fn-builder-railpack-no-cache() {
+ declare APP="$1"
+
+ fn-plugin-property-get "builder-railpack" "$APP" "no-cache" ""
+}
diff --git a/plugins/builder-railpack/plugin.toml b/plugins/builder-railpack/plugin.toml
new file mode 100644
index 00000000000..6fa72020b92
--- /dev/null
+++ b/plugins/builder-railpack/plugin.toml
@@ -0,0 +1,4 @@
+[plugin]
+description = "dokku core builder-railpack plugin"
+version = "0.36.7"
+[plugin.config]
diff --git a/plugins/builder-railpack/post-app-clone-setup b/plugins/builder-railpack/post-app-clone-setup
new file mode 100755
index 00000000000..0ff83fc92d3
--- /dev/null
+++ b/plugins/builder-railpack/post-app-clone-setup
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+trigger-builder-railpack-post-app-clone-setup() {
+ declare desc="sets up properties for new app"
+ declare trigger="post-app-clone-setup"
+ declare OLD_APP="$1" NEW_APP="$2"
+
+ fn-plugin-property-clone "builder-railpack" "$OLD_APP" "$NEW_APP"
+}
+
+trigger-builder-railpack-post-app-clone-setup "$@"
diff --git a/plugins/builder-railpack/post-app-rename-setup b/plugins/builder-railpack/post-app-rename-setup
new file mode 100755
index 00000000000..10effd4e57d
--- /dev/null
+++ b/plugins/builder-railpack/post-app-rename-setup
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+trigger-builder-railpack-post-app-rename-setup() {
+ declare desc="updates settings for new app"
+ declare trigger="post-app-rename-setup"
+ declare OLD_APP="$1" NEW_APP="$2"
+
+ fn-plugin-property-clone "builder-railpack" "$OLD_APP" "$NEW_APP"
+ fn-plugin-property-destroy "builder-railpack" "$OLD_APP"
+}
+
+trigger-builder-railpack-post-app-rename-setup "$@"
diff --git a/plugins/builder-railpack/post-delete b/plugins/builder-railpack/post-delete
new file mode 100755
index 00000000000..5879fc04652
--- /dev/null
+++ b/plugins/builder-railpack/post-delete
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+trigger-builder-railpack-post-delete() {
+ declare desc="destroys the builder-railpack properties for a given app"
+ declare trigger="post-delete"
+ declare APP="$1"
+
+ fn-plugin-property-destroy "builder-railpack" "$APP"
+ rm -rf "${DOKKU_LIB_ROOT}/data/builder-railpack/$APP"
+}
+
+trigger-builder-railpack-post-delete "$@"
diff --git a/plugins/builder-railpack/report b/plugins/builder-railpack/report
new file mode 100755
index 00000000000..6fbd0d85549
--- /dev/null
+++ b/plugins/builder-railpack/report
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+cmd-builder-railpack-report-single "$@"
diff --git a/plugins/builder-railpack/subcommands/default b/plugins/builder-railpack/subcommands/default
new file mode 100755
index 00000000000..cc0e6748a01
--- /dev/null
+++ b/plugins/builder-railpack/subcommands/default
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+source "$PLUGIN_AVAILABLE_PATH/builder-railpack/help-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+cmd-builder-railpack-help "builder-railpack:help"
diff --git a/plugins/builder-railpack/subcommands/report b/plugins/builder-railpack/subcommands/report
new file mode 100755
index 00000000000..9801d5f29df
--- /dev/null
+++ b/plugins/builder-railpack/subcommands/report
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+source "$PLUGIN_AVAILABLE_PATH/builder-railpack/internal-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+cmd-builder-railpack-report "$@"
diff --git a/plugins/builder-railpack/subcommands/set b/plugins/builder-railpack/subcommands/set
new file mode 100755
index 00000000000..6001f0c6f8a
--- /dev/null
+++ b/plugins/builder-railpack/subcommands/set
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
+source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
+set -eo pipefail
+[[ $DOKKU_TRACE ]] && set -x
+
+cmd-builder-railpack-set() {
+ declare desc="set or clear a builder-railpack property for an app"
+ declare cmd="builder-railpack:set"
+ [[ "$1" == "$cmd" ]] && shift 1
+ declare APP="$1" KEY="$2" VALUE="$3"
+ local VALID_KEYS=("railpackjson-path" "no-cache")
+ [[ "$APP" == "--global" ]] || verify_app_name "$APP"
+
+ [[ -z "$KEY" ]] && dokku_log_fail "No key specified"
+
+ if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then
+ dokku_log_fail "Invalid key specified, valid keys include: railpackjson-path, no-cache"
+ fi
+
+ if [[ -n "$VALUE" ]]; then
+ dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}"
+ fn-plugin-property-write "builder-railpack" "$APP" "$KEY" "$VALUE"
+ else
+ dokku_log_info2_quiet "Unsetting ${KEY}"
+ fn-plugin-property-delete "builder-railpack" "$APP" "$KEY"
+ fi
+}
+
+cmd-builder-railpack-set "$@"
diff --git a/plugins/common/exec.go b/plugins/common/exec.go
index c5790ba6e37..367449b8bff 100644
--- a/plugins/common/exec.go
+++ b/plugins/common/exec.go
@@ -29,6 +29,9 @@ type ExecCommandInput struct {
// Env is the environment variables to pass to the command
Env map[string]string
+ // PrintCommand prints the command before executing
+ PrintCommand bool
+
// Stdin is the stdin of the command
Stdin io.Reader
@@ -141,7 +144,7 @@ func CallExecCommandWithContext(ctx context.Context, input ExecCommandInput) (Ex
cmd.Cwd = input.WorkingDirectory
}
- if os.Getenv("DOKKU_TRACE") == "1" {
+ if input.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" {
argsSt := ""
if len(cmd.Args) > 0 {
argsSt = strings.Join(cmd.Args, " ")
diff --git a/plugins/common/plugn.go b/plugins/common/plugn.go
index 8ce905ac511..4acf58a21b3 100644
--- a/plugins/common/plugn.go
+++ b/plugins/common/plugn.go
@@ -19,6 +19,9 @@ type PlugnTriggerInput struct {
// Env is the environment variables to pass to the trigger
Env map[string]string
+ // PrintCommand prints the command before executing
+ PrintCommand bool
+
// Stdin is the stdin of the command
Stdin io.Reader
@@ -55,7 +58,7 @@ func CallPlugnTriggerWithContext(ctx context.Context, input PlugnTriggerInput) (
StreamStderr: input.StreamStderr,
})
- if os.Getenv("DOKKU_TRACE") == "1" {
+ if input.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" {
for _, line := range strings.Split(result.Stderr, "\n") {
LogDebug(fmt.Sprintf("plugn trigger %s stderr: %s", input.Trigger, line))
}
diff --git a/plugins/common/sftp.go b/plugins/common/sftp.go
index 1d8ade339d4..2758ff4e34a 100644
--- a/plugins/common/sftp.go
+++ b/plugins/common/sftp.go
@@ -219,7 +219,7 @@ func (task SftpCopyTask) Execute(ctx context.Context) (SftpCopyResult, error) {
}, ctx.Err()
}
- if task.PrintCommand {
+ if task.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" {
LogDebug(fmt.Sprintf("ssh %s@%s cp %s %v", task.Username, task.Hostname, task.SourcePath, task.DestinationPath))
}
diff --git a/plugins/common/ssh.go b/plugins/common/ssh.go
index c23f63bc92f..f823c75c3f0 100644
--- a/plugins/common/ssh.go
+++ b/plugins/common/ssh.go
@@ -203,6 +203,7 @@ type SshTask struct {
// PrintCommand prints the command before executing
PrintCommand bool
+
// StreamStdio prints stdout and stderr directly to os.Stdout/err as
// the command runs.
StreamStdio bool
@@ -334,7 +335,7 @@ func (task SshTask) Execute(ctx context.Context) (SshResult, error) {
command = "sudo"
}
- if task.PrintCommand {
+ if task.PrintCommand || os.Getenv("DOKKU_TRACE") == "1" {
LogDebug(fmt.Sprintf("ssh %s@%s %s %v", task.Username, task.Hostname, command, commandArgs))
}
diff --git a/plugins/docker-options/docker-args-deploy b/plugins/docker-options/docker-args-deploy
index fc768a0cf28..35ea4bc97dd 100755
--- a/plugins/docker-options/docker-args-deploy
+++ b/plugins/docker-options/docker-args-deploy
@@ -47,7 +47,7 @@ trigger-docker-options-docker-args() {
*)
case "$IMAGE_SOURCE_TYPE" in
- dockerfile | nixpacks)
+ dockerfile | nixpacks | railpack)
case "$line" in
--link* | -v* | --volume*)
continue
diff --git a/tests/unit/builder-railpack.bats b/tests/unit/builder-railpack.bats
new file mode 100644
index 00000000000..e0dcc5f6cdd
--- /dev/null
+++ b/tests/unit/builder-railpack.bats
@@ -0,0 +1,62 @@
+#!/usr/bin/env bats
+
+load test_helper
+
+setup_file() {
+ global_setup
+ install_railpack
+ docker run --rm --privileged -d --name buildkit moby/buildkit:latest
+ 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() {
+ create_app
+}
+
+teardown() {
+ destroy_app
+ global_teardown
+}
+
+@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 inject_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'
+}
+
+inject_requirements_txt() {
+ local APP="$1"
+ local APP_REPO_DIR="$2"
+ [[ -z "$APP" ]] && local APP="$TEST_APP"
+ echo "flask" >>"$APP_REPO_DIR/requirements.txt"
+}
diff --git a/tests/unit/report.bats b/tests/unit/report.bats
index a5f7271b605..719af8c800b 100644
--- a/tests/unit/report.bats
+++ b/tests/unit/report.bats
@@ -103,6 +103,7 @@ teardown() {
builder-herokuish
builder-lambda
builder-nixpacks
+ builder-railpack
builder-pack
buildpacks
caddy
diff --git a/tests/unit/scheduler-k3s-1.bats b/tests/unit/scheduler-k3s-1.bats
index f9fbcd45578..b89be6d0ea9 100644
--- a/tests/unit/scheduler-k3s-1.bats
+++ b/tests/unit/scheduler-k3s-1.bats
@@ -11,7 +11,7 @@ setup() {
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
}
-teardown_() {
+teardown() {
global_teardown
dokku nginx:start
uninstall_k3s || true
diff --git a/tests/unit/scheduler-k3s-2.bats b/tests/unit/scheduler-k3s-2.bats
index b1c47e8816e..978bfa53140 100644
--- a/tests/unit/scheduler-k3s-2.bats
+++ b/tests/unit/scheduler-k3s-2.bats
@@ -11,7 +11,7 @@ setup() {
export KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
}
-teardown_() {
+teardown() {
global_teardown
dokku nginx:start
uninstall_k3s || true
diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash
index 34ce53bcbc7..c53880f2497 100644
--- a/tests/unit/test_helper.bash
+++ b/tests/unit/test_helper.bash
@@ -660,6 +660,12 @@ install_nixpacks() {
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"