From 5a787af3ae5e5858a6c2e6809f4ce94d0f8ede03 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 23 Nov 2025 01:39:14 -0500 Subject: [PATCH 1/4] feat: add support for specifying buildpacks via app.json The order of detection is: - buildpacks:set - app.json - .buildpacks - auto-detected from codebase Closes #7102 --- plugins/app-json/appjson.go | 9 +++++++++ plugins/buildpacks/functions.go | 33 +++++++++++++++++++++++++++++++++ plugins/buildpacks/go.mod | 6 ++++++ plugins/buildpacks/go.sum | 6 ++++++ plugins/buildpacks/triggers.go | 11 +++++++++-- plugins/scheduler-k3s/go.mod | 16 ++++++++-------- 6 files changed, 71 insertions(+), 10 deletions(-) diff --git a/plugins/app-json/appjson.go b/plugins/app-json/appjson.go index 3133330c1..ddb045a11 100644 --- a/plugins/app-json/appjson.go +++ b/plugins/app-json/appjson.go @@ -26,6 +26,9 @@ var ( // AppJSON is a struct that represents an app.json file as understood by Dokku type AppJSON struct { + // Buildpacks is a list of buildpacks to use for the app + Buildpacks []Buildpack `json:"buildpacks"` + // Cron is a list of cron tasks to execute Cron []CronTask `json:"cron"` @@ -51,6 +54,12 @@ type AppJSON struct { } `json:"scripts"` } +// Buildpack is a struct that represents a single buildpack +type Buildpack struct { + // URL is the URL of the buildpack + URL string `json:"url"` +} + // CronTask is a struct that represents a single cron task from an app.json file type CronTask struct { // Command is the command to execute diff --git a/plugins/buildpacks/functions.go b/plugins/buildpacks/functions.go index 47acbbc78..37d8e9515 100644 --- a/plugins/buildpacks/functions.go +++ b/plugins/buildpacks/functions.go @@ -8,9 +8,38 @@ import ( "regexp" "strings" + appjson "github.com/dokku/dokku/plugins/app-json" "github.com/dokku/dokku/plugins/common" ) +// getBuildpacks returns the buildpacks for a given app +// the order of application is: +// 1. .buildpacks: if it exists, use it +// 2. app.json: if it exists and has buildpacks, use them +// 3. buildpack properties: if they exist, use them +// 4. buildpack detection +func getBuildpacks(appName string) ([]string, error) { + buildpacks, err := common.PropertyListGet("buildpacks", appName, "buildpacks") + if err != nil { + return buildpacks, err + } + + appJSON, err := appjson.GetAppJSON(appName) + if err != nil { + return buildpacks, err + } + + if len(appJSON.Buildpacks) == 0 { + return buildpacks, nil + } + + for _, b := range appJSON.Buildpacks { + buildpacks = append(buildpacks, b.URL) + } + + return buildpacks, nil +} + func rewriteBuildpacksFile(sourceWorkDir string) error { buildpacksPath := filepath.Join(sourceWorkDir, ".buildpacks") if !common.FileExists(buildpacksPath) { @@ -27,6 +56,10 @@ func rewriteBuildpacksFile(sourceWorkDir string) error { continue } + if strings.HasPrefix(buildpack, "#") { + continue + } + buildpack, err = validBuildpackURL(buildpack) if err != nil { return fmt.Errorf("Unable to parse .buildpacks file, line %d: %s", i, err) diff --git a/plugins/buildpacks/go.mod b/plugins/buildpacks/go.mod index 54905d796..ae4000145 100644 --- a/plugins/buildpacks/go.mod +++ b/plugins/buildpacks/go.mod @@ -3,6 +3,7 @@ module github.com/dokku/dokku/plugins/buildpacks go 1.25.5 require ( + github.com/dokku/dokku/plugins/app-json v0.0.0-00010101000000-000000000000 github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 github.com/spf13/pflag v1.0.10 ) @@ -12,6 +13,7 @@ require ( github.com/fatih/color v1.19.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kr/fs v0.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -21,9 +23,13 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.10 // indirect github.com/ryanuber/columnize v2.1.2+incompatible // indirect + github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // indirect golang.org/x/crypto v0.50.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect + k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect ) +replace github.com/dokku/dokku/plugins/app-json => ../app-json + replace github.com/dokku/dokku/plugins/common => ../common diff --git a/plugins/buildpacks/go.sum b/plugins/buildpacks/go.sum index c97fc9665..1db1549b6 100644 --- a/plugins/buildpacks/go.sum +++ b/plugins/buildpacks/go.sum @@ -10,6 +10,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -36,6 +38,8 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYjaeN8l2mc4kSz2iMiCDQxPEyltUV/I= +github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= @@ -53,3 +57,5 @@ golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= +k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= diff --git a/plugins/buildpacks/triggers.go b/plugins/buildpacks/triggers.go index 37f1c33d7..fce9ecd5d 100644 --- a/plugins/buildpacks/triggers.go +++ b/plugins/buildpacks/triggers.go @@ -74,7 +74,7 @@ func TriggerPostDelete(appName string) error { // TriggerPostExtract writes a .buildpacks file into the app func TriggerPostExtract(appName string, sourceWorkDir string) error { - buildpacks, err := common.PropertyListGet("buildpacks", appName, "buildpacks") + buildpacks, err := getBuildpacks(appName) if err != nil { return nil } @@ -91,13 +91,20 @@ func TriggerPostExtract(appName string, sourceWorkDir string) error { w := bufio.NewWriter(file) for _, buildpack := range buildpacks { + buildpack, err = validBuildpackURL(buildpack) + if err != nil { + return err + } + fmt.Fprintln(w, buildpack) } if err = w.Flush(); err != nil { return fmt.Errorf("Error writing .buildpacks file: %s", err.Error()) } - file.Chmod(0600) + if err = file.Chmod(0600); err != nil { + return fmt.Errorf("Error setting .buildpacks file permissions: %s", err.Error()) + } return nil } diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index d7868e783..f19ee7996 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -5,14 +5,14 @@ go 1.25.5 require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/cert-manager/cert-manager v1.20.2 - github.com/dokku/dokku/plugins/app-json v0.0.0-20250618161309-8d0c35f1333c - github.com/dokku/dokku/plugins/common v0.0.0-20250618161309-8d0c35f1333c - github.com/dokku/dokku/plugins/config v0.0.0-20250618161309-8d0c35f1333c - github.com/dokku/dokku/plugins/cron v0.0.0-20250618161309-8d0c35f1333c - github.com/dokku/dokku/plugins/docker-options v0.0.0-20250618161309-8d0c35f1333c - github.com/dokku/dokku/plugins/logs v0.0.0-20250618161309-8d0c35f1333c - github.com/dokku/dokku/plugins/nginx-vhosts v0.0.0-20250618161309-8d0c35f1333c - github.com/dokku/dokku/plugins/registry v0.0.0-20250618161309-8d0c35f1333c + github.com/dokku/dokku/plugins/app-json v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/cron v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/docker-options v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/logs v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/nginx-vhosts v0.0.0-00010101000000-000000000000 + github.com/dokku/dokku/plugins/registry v0.0.0-00010101000000-000000000000 github.com/fatih/color v1.19.0 github.com/fluxcd/pkg/kustomize v1.29.0 github.com/go-openapi/jsonpointer v0.23.0 From 44dbbe6a3473b21d9beb6e6d313c2fbab9ab80d2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 28 Nov 2025 02:31:13 -0500 Subject: [PATCH 2/4] fix: return buildpacks specified by the buildpacks plugin --- plugins/buildpacks/functions.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/buildpacks/functions.go b/plugins/buildpacks/functions.go index 37d8e9515..7037cf516 100644 --- a/plugins/buildpacks/functions.go +++ b/plugins/buildpacks/functions.go @@ -24,6 +24,10 @@ func getBuildpacks(appName string) ([]string, error) { return buildpacks, err } + if len(buildpacks) > 0 { + return buildpacks, nil + } + appJSON, err := appjson.GetAppJSON(appName) if err != nil { return buildpacks, err From b0b11bcf263df643ee70bb8dc602da31882d4f0a Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 18 Apr 2026 05:43:44 -0400 Subject: [PATCH 3/4] fix: remove buildpacks from dockerfile test app and add tests for app.json buildpacks The buildpacks entry in tests/apps/dockerfile/app.json caused the post-extract trigger to write a .buildpacks file during Dockerfile-based deploys, breaking builder detection. Remove the entry and add Go unit tests for getBuildpacks/validBuildpackURL, a bats integration test for deploying with app.json buildpacks, and documentation for the new feature. --- docs/appendices/file-formats/app-json.md | 19 +++ .../builders/buildpack-management.md | 18 +++ plugins/buildpacks/buildpacks_test.go | 141 ++++++++++++++++++ plugins/buildpacks/go.mod | 5 + plugins/buildpacks/go.sum | 1 + tests.mk | 4 +- tests/apps/dockerfile/app.json | 5 - tests/unit/buildpacks.bats | 27 ++++ 8 files changed, 214 insertions(+), 6 deletions(-) create mode 100644 plugins/buildpacks/buildpacks_test.go diff --git a/docs/appendices/file-formats/app-json.md b/docs/appendices/file-formats/app-json.md index 53924fabc..6532c93b2 100644 --- a/docs/appendices/file-formats/app-json.md +++ b/docs/appendices/file-formats/app-json.md @@ -5,6 +5,25 @@ > [!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 diff --git a/docs/deployment/builders/buildpack-management.md b/docs/deployment/builders/buildpack-management.md index c577e26c9..a2b876b9f 100644 --- a/docs/deployment/builders/buildpack-management.md +++ b/docs/deployment/builders/buildpack-management.md @@ -130,6 +130,24 @@ This will use the latest commit on the `master` branch of the specified buildpac 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: diff --git a/plugins/buildpacks/buildpacks_test.go b/plugins/buildpacks/buildpacks_test.go new file mode 100644 index 000000000..30fe99dae --- /dev/null +++ b/plugins/buildpacks/buildpacks_test.go @@ -0,0 +1,141 @@ +package buildpacks + +import ( + "os" + "path/filepath" + "testing" + + . "github.com/onsi/gomega" +) + +func setupTestEnvironment(t *testing.T) string { + t.Helper() + tmpDir, err := os.MkdirTemp("", "buildpacks-test") + if err != nil { + t.Fatal(err) + } + + os.Setenv("DOKKU_LIB_ROOT", tmpDir) + os.Setenv("PLUGIN_PATH", "/var/lib/dokku/plugins") + os.Setenv("PLUGIN_ENABLED_PATH", "/var/lib/dokku/plugins/enabled") + + return tmpDir +} + +func teardownTestEnvironment(tmpDir string) { + os.RemoveAll(tmpDir) +} + +func TestValidBuildpackURL(t *testing.T) { + RegisterTestingT(t) + + url, err := validBuildpackURL("") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Must specify a buildpack")) + + url, err = validBuildpackURL("heroku/nodejs") + Expect(err).NotTo(HaveOccurred()) + Expect(url).To(Equal("https://github.com/heroku/heroku-buildpack-nodejs.git")) + + url, err = validBuildpackURL("heroku/python") + Expect(err).NotTo(HaveOccurred()) + Expect(url).To(Equal("https://github.com/heroku/heroku-buildpack-python.git")) + + url, err = validBuildpackURL("heroku-community/apt") + Expect(err).NotTo(HaveOccurred()) + Expect(url).To(Equal("https://github.com/heroku/heroku-buildpack-apt.git")) + + url, err = validBuildpackURL("https://github.com/heroku/heroku-buildpack-nodejs.git") + Expect(err).NotTo(HaveOccurred()) + Expect(url).To(Equal("https://github.com/heroku/heroku-buildpack-nodejs.git")) + + url, err = validBuildpackURL("https://github.com/heroku/heroku-buildpack-nodejs") + Expect(err).NotTo(HaveOccurred()) + Expect(url).To(Equal("https://github.com/heroku/heroku-buildpack-nodejs")) + + url, err = validBuildpackURL("git://github.com/heroku/heroku-buildpack-nodejs.git") + Expect(err).NotTo(HaveOccurred()) + Expect(url).To(Equal("git://github.com/heroku/heroku-buildpack-nodejs.git")) + + _, err = validBuildpackURL("nodejs") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Invalid buildpack specified")) + + _, err = validBuildpackURL("/nodejs") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Invalid buildpack specified")) +} + +func TestGetBuildpacksEmpty(t *testing.T) { + RegisterTestingT(t) + tmpDir := setupTestEnvironment(t) + defer teardownTestEnvironment(tmpDir) + + buildpacks, err := getBuildpacks("test-app") + Expect(err).NotTo(HaveOccurred()) + Expect(buildpacks).To(BeEmpty()) +} + +func TestGetBuildpacksFromProperties(t *testing.T) { + RegisterTestingT(t) + tmpDir := setupTestEnvironment(t) + defer teardownTestEnvironment(tmpDir) + + propDir := filepath.Join(tmpDir, "config", "buildpacks", "test-app") + Expect(os.MkdirAll(propDir, 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(propDir, "buildpacks"), []byte("https://github.com/heroku/heroku-buildpack-nodejs.git\nhttps://github.com/heroku/heroku-buildpack-python.git\n"), 0644)).To(Succeed()) + + buildpacks, err := getBuildpacks("test-app") + Expect(err).NotTo(HaveOccurred()) + Expect(buildpacks).To(HaveLen(2)) + Expect(buildpacks[0]).To(Equal("https://github.com/heroku/heroku-buildpack-nodejs.git")) + Expect(buildpacks[1]).To(Equal("https://github.com/heroku/heroku-buildpack-python.git")) +} + +func TestGetBuildpacksFromAppJSON(t *testing.T) { + RegisterTestingT(t) + tmpDir := setupTestEnvironment(t) + defer teardownTestEnvironment(tmpDir) + + appJSONDir := filepath.Join(tmpDir, "data", "app-json", "test-app") + Expect(os.MkdirAll(appJSONDir, 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(appJSONDir, "app.json"), []byte(`{"buildpacks": [{"url": "https://github.com/heroku/heroku-buildpack-nodejs.git"}]}`), 0644)).To(Succeed()) + + buildpacks, err := getBuildpacks("test-app") + Expect(err).NotTo(HaveOccurred()) + Expect(buildpacks).To(HaveLen(1)) + Expect(buildpacks[0]).To(Equal("https://github.com/heroku/heroku-buildpack-nodejs.git")) +} + +func TestGetBuildpacksPropertiesOverrideAppJSON(t *testing.T) { + RegisterTestingT(t) + tmpDir := setupTestEnvironment(t) + defer teardownTestEnvironment(tmpDir) + + propDir := filepath.Join(tmpDir, "config", "buildpacks", "test-app") + Expect(os.MkdirAll(propDir, 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(propDir, "buildpacks"), []byte("https://github.com/heroku/heroku-buildpack-ruby.git\n"), 0644)).To(Succeed()) + + appJSONDir := filepath.Join(tmpDir, "data", "app-json", "test-app") + Expect(os.MkdirAll(appJSONDir, 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(appJSONDir, "app.json"), []byte(`{"buildpacks": [{"url": "https://github.com/heroku/heroku-buildpack-nodejs.git"}]}`), 0644)).To(Succeed()) + + buildpacks, err := getBuildpacks("test-app") + Expect(err).NotTo(HaveOccurred()) + Expect(buildpacks).To(HaveLen(1)) + Expect(buildpacks[0]).To(Equal("https://github.com/heroku/heroku-buildpack-ruby.git")) +} + +func TestGetBuildpacksAppJSONEmptyArray(t *testing.T) { + RegisterTestingT(t) + tmpDir := setupTestEnvironment(t) + defer teardownTestEnvironment(tmpDir) + + appJSONDir := filepath.Join(tmpDir, "data", "app-json", "test-app") + Expect(os.MkdirAll(appJSONDir, 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(appJSONDir, "app.json"), []byte(`{"buildpacks": []}`), 0644)).To(Succeed()) + + buildpacks, err := getBuildpacks("test-app") + Expect(err).NotTo(HaveOccurred()) + Expect(buildpacks).To(BeEmpty()) +} diff --git a/plugins/buildpacks/go.mod b/plugins/buildpacks/go.mod index ae4000145..5afa00299 100644 --- a/plugins/buildpacks/go.mod +++ b/plugins/buildpacks/go.mod @@ -11,6 +11,7 @@ require ( require ( github.com/alexellis/go-execute/v2 v2.2.1 // indirect github.com/fatih/color v1.19.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect @@ -18,15 +19,19 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/melbahja/goph v1.5.0 // indirect + github.com/onsi/gomega v1.39.1 // indirect github.com/otiai10/copy v1.14.1 // indirect github.com/otiai10/mint v1.6.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.10 // indirect github.com/ryanuber/columnize v2.1.2+incompatible // indirect github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.50.0 // indirect + golang.org/x/net v0.52.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect ) diff --git a/plugins/buildpacks/go.sum b/plugins/buildpacks/go.sum index 1db1549b6..957619840 100644 --- a/plugins/buildpacks/go.sum +++ b/plugins/buildpacks/go.sum @@ -55,6 +55,7 @@ golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= diff --git a/tests.mk b/tests.mk index c31d42db0..d13240a98 100644 --- a/tests.mk +++ b/tests.mk @@ -149,6 +149,7 @@ ci-go-coverage: @$(MAKE) ci-go-coverage-plugin PLUGIN_NAME=common @$(MAKE) ci-go-coverage-plugin PLUGIN_NAME=config @$(MAKE) ci-go-coverage-plugin PLUGIN_NAME=network + @$(MAKE) ci-go-coverage-plugin PLUGIN_NAME=buildpacks ci-go-coverage-plugin: mkdir -p test-results/coverage @@ -177,9 +178,10 @@ go-tests: @$(MAKE) go-test-plugin PLUGIN_NAME=common @$(MAKE) go-test-plugin PLUGIN_NAME=config @$(MAKE) go-test-plugin PLUGIN_NAME=network + @$(MAKE) go-test-plugin PLUGIN_NAME=buildpacks go-test-plugin: - cd plugins/$(PLUGIN_NAME) && go get github.com/onsi/gomega && DOKKU_ROOT=/home/dokku go test -v -p 1 -race -mod=readonly || exit $$? + cd plugins/$(PLUGIN_NAME) && go get github.com/onsi/gomega && DOKKU_ROOT=/home/dokku DOKKU_LIB_ROOT=/var/lib/dokku go test -v -p 1 -race -mod=readonly || exit $$? go-test-plugin-in-docker: @echo running go unit tests... diff --git a/tests/apps/dockerfile/app.json b/tests/apps/dockerfile/app.json index bd0d70165..39127109e 100644 --- a/tests/apps/dockerfile/app.json +++ b/tests/apps/dockerfile/app.json @@ -3,11 +3,6 @@ "dokku-postgres", "dokku-redis" ], - "buildpacks": [ - { - "url": "https://github.com/heroku/heroku-buildpack-nodejs" - } - ], "description": "Used in dokku's test suite", "env": { "SECRET_TOKEN": { diff --git a/tests/unit/buildpacks.bats b/tests/unit/buildpacks.bats index e4d24fb39..59e9b4438 100644 --- a/tests/unit/buildpacks.bats +++ b/tests/unit/buildpacks.bats @@ -336,6 +336,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" From 08596c9648af38bb87c5822538a30519e63fb2b5 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 18 Apr 2026 20:29:05 -0400 Subject: [PATCH 4/4] fix: skip app.json buildpacks for image-based deploys When deploying via git:from-image, the source image may contain an app.json with buildpacks entries that are not relevant. Check for the source-image property and skip app.json buildpacks when set. --- plugins/buildpacks/functions.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/buildpacks/functions.go b/plugins/buildpacks/functions.go index 7037cf516..89beebc3a 100644 --- a/plugins/buildpacks/functions.go +++ b/plugins/buildpacks/functions.go @@ -28,6 +28,14 @@ func getBuildpacks(appName string) ([]string, error) { return buildpacks, nil } + results, _ := common.CallPlugnTrigger(common.PlugnTriggerInput{ + Trigger: "git-get-property", + Args: []string{appName, "source-image"}, + }) + if results.StdoutContents() != "" { + return buildpacks, nil + } + appJSON, err := appjson.GetAppJSON(appName) if err != nil { return buildpacks, err