-
Notifications
You must be signed in to change notification settings - Fork 455
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add pack option to the builder options for cloud native buildpacks #916
base: main
Are you sure you want to change the base?
Conversation
buildpacks, | ||
"-t", config.absolute_image, | ||
"-t", config.latest_image, | ||
"--env", "BP_IMAGE_LABELS=service=#{config.service}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kamal expects there to be a service label, this automatically adds the label via the paketo-buildpacks/image-labels
buildpack.
end | ||
|
||
def buildpacks | ||
(pack_buildpacks << "paketo-buildpacks/image-labels").map { |buildpack| ["--buildpack", buildpack] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding this buildpack automatically so that we can label the image for Kamal
@@ -37,6 +37,16 @@ builder: | |||
arch: arm64 | |||
host: ssh://docker@docker-builder | |||
|
|||
# Buildpack configuration | |||
# | |||
# The build configuration for using pack to build a Cloud Native Buildpack image. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add mention of project.toml to set your excluded options. https://buildpacks.io/docs/for-app-developers/how-to/build-inputs/use-project-toml/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I was thinking about this and removing context: "."
it doesn't matter as much since it's using the git clone. The exclusion list is really only relevant when you're using "."
as your build context.
hey @nickhammond and @dhh, does this change resolve the custom build issue like using the builder of choice. e.g. docker build cloud? |
Oh, thanks for the awareness. I would be glad if you could help me make a PR for that since I don't know how to build gems or modify it for now |
|
||
private | ||
def platform | ||
"linux/#{local_arches.first}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pack only supports building for one platform, make it obvious in docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can add a validation for this in Kamal::Configuration::Validator::Builder
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@djmb Added a validation for this.
This is fascinating work, @nickhammond. I'm surprised by how unobtrusive it is! But I'd like to understand the whole flow better. I'm not sure this is going to be all that relevant for Rails apps that now already come with well-optimized Dockerfiles out of the box, but I could see how that may well be different if you're doing a Sinatra app or some app from another framework that doesn't provide that. Could you show how the entire flow would go with, say, a Sinatra app, using buildpacks, and deploying on something like Digital Ocean? Want to make sure that this isn't tied to any one company or platform. |
Hey @dhh, thanks for taking a look! I think adding support for buildpacks will be great for the adoption of Kamal but you can always still reach for the sharper tool(a full Dockerfile) when needed. I built out a few hello world examples, the main thing is just making sure your app boots on port 80 for kamal-proxy or just ensuring that you set your Here are the hello world apps that I built and tested out on Digital Ocean and wrote a more detailed overview for the whole process as well.
|
@nickhammond thanks for all your investigations and opening this PR. ❤️
@dhh 👋 It's been a while. As Cloud Native Buildpacks (CNB) maintainer I'm biased and would love to see this supported in kamal. :) Nick touches on this in his blog, but if it's any assurance CNB as an upstream project is a CNCF Incubation project which pushes for not being a single vendor OSS project. In fact, the project was started from the get go by two companies, Heroku & Pivotal. It's really about bringing that Heroku magic to container image building, transforming your app source code into an OCI image (No |
Started on the docs in this kamal-site PR basecamp/kamal-site#117. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nickhammond - I noticed in your sample apps, that you've set the context for the builder to .
, which avoids using the git clone for building.
Is that just a preference or is there any reason it would be required?
|
||
private | ||
def platform | ||
"linux/#{local_arches.first}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can add a validation for this in Kamal::Configuration::Validator::Builder
.
lib/kamal/commands/builder/base.rb
Outdated
@@ -33,7 +34,7 @@ def info | |||
end | |||
|
|||
def inspect_builder | |||
docker :buildx, :inspect, builder_name unless docker_driver? | |||
docker :buildx, :inspect, builder_name unless docker_driver? || pack? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could extract a buildx?
method here?
def buildx?
!docker_driver? && !pack?
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@djmb We could also run pack builder inspect
which returns a bunch of information about the default builder. It's a lot of information but might be useful to help triage if you're not sure what builder you're using. The Pack CLI lets you set your default builder so I have mine set to heroku/builder:24
via pack config default-builder heroku/builder:24
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nickhammond Does this mean I can now pass my builder name to kamal?
Buildx cloud_builder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexohre No, I don't think there's a PR open for that, just the discussion here #914 (comment)
"-t", config.absolute_image, | ||
"-t", config.latest_image, | ||
"--env", "BP_IMAGE_LABELS=service=#{config.service}", | ||
*argumentize("--env", secrets, sensitive: true), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is using environment variables the standard way to get secrets into a buildpack?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@djmb Yes, they only have the --env
flag.
I just tested building with a few secrets because I was concerned they'd end up in the final image but they don't.
I just found this in the docs site though. TLDR; It's just a build-time env var, they're not available at image runtime. So they're naturally "secret", neat.
https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/cli/pack_build/#options
-e, --env stringArray Build-time environment variable, in the form 'VAR=VALUE' or 'VAR'.
When using latter value-less form, value will be taken from current
environment at the time this command is executed.
This flag may be specified multiple times and will override
individual values defined by --env-file.
Repeat for each env in order (comma-separated lists not accepted)
NOTE: These are NOT available at image runtime.
@djmb I usually just use the old context style when initially getting a project going since it's a bit faster. I've removed the context on all of the sample apps and they're all deploying successfully. To be honest though I'm not super familiar with how the clone process actually works. I'm passing in the same Full pack command that's running:
Vs. Docker command:
With both though I'm seeing the clone steps, does it clone into that temp directory and then drop into it?
|
"-t", config.absolute_image, | ||
"-t", config.latest_image, | ||
"--env", "BP_IMAGE_LABELS=service=#{config.service}", | ||
*argumentize("--env", args), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With docker, build args are passed as --build-arg
and with Kamal you set them via:
args:
ENVIRONMENT: production
You'd still set "build args" with pack via the same args
section but they ultimately get passed as --env
to the pack command. Trying to reduce confusion of when to use env/arg if you're testing out your builds.
docker(:push, config.latest_image) | ||
end | ||
|
||
def remove;end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're not actually creating anything with the buildpack setup so there isn't anything to remove. Should we still puts
something out to the user?
Highlighting some build time specs with a traditional build with a Dockerfile vs. buildpacks. I ran these on an M2 Max, 64GB, Docker is capped at 4GB RAM and 4 CPUs. All timing is based on running Buildpacks
Full pack command that Kamal is running
Dockerfile (The one that ships with Rails - MySQL, Ruby 3.3.5, Rails main, included below)
Dockerfile
A few takeaways:
Caching your bundle install with a Dockerfile
Heroku's buildpack automatically utilizing a cache for your gems
|
Hi! pack build checking for an updated image should take at most 1-2 seconds to make a couple of requests to the registry, not 15? Are you sure there isn't something else at play? (If the local images are up to date, nothing is actually pulled after the update check.) If needed, the update check itself can be skipped using |
@edmorley You're right, I found the |
This PR introduces Cloud Native Buildpacks to the list of builder options for Kamal.
This opens up the option to utilize buildpacks instead of writing a Dockerfile from scratch for each app that you want to deploy with Kamal. The end result is still an OCI-compliant docker image that you can run the same as the currently built docker images with Kamal.
You can also use any buildpacks or builders that you'd like so if you prefer some of the Paketo buildpacks instead you can use those too. The example below is utilizing Heroku's builder with the ruby and procfile buildpack which gives you the familiar Heroku build process when you deploy your application. Auto-detection of bundler, cache management of gem and asset installation, and various other features that come along with those.
With this PR you'd need to have pack installed as well as Docker and then within your deploy.yml change your builder to:
The default process that the buildpack tries to boot is the web process, you can add a Procfile for this:
And lastly, buildpacks don't bind to a default port so you'll either need to set
proxy.app_port
(Kamal 2.0 / kamal-proxy) to your application port or set your app to use port 80 which is the defaultkamal-proxy
port.Buildpacks work in a detect and then build flow. The detect step looks for common files or checks that indicate it is indeed a ruby application by looking for a Gemfile.lock for instance. If the detect step passes then it triggers the build phase which essentially triggers a bundle install in this example.
With heroku/builder:24 so far I've found that the image size is about the same, it's only 2mb off for a 235mb image. Build time is typically faster with pack but depends on how well you've optimized your Dockerfile. The win though is not having to think about how to cache your gem installs, node installs or any other package manager installs that have a buildpack. It's also following the common conventions for building containers and various stumbling blocks that Heroku and others have been blazing through over the years.
Kamal discussion: #795
Heroku discussion: heroku/buildpacks#6
Heroku official buildpacks: https://devcenter.heroku.com/articles/buildpacks
Heroku 3rd party buildpacks: https://elements.heroku.com/buildpacks
Full setup overview: https://www.fromthekeyboard.com/deploying-a-rails-app-with-kamal-heroku-style/
Todos:
Discuss potential for a remote pack optionOut of scopekamal build create
do when using buildpacks? Just point to the install docs? https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/ - Since you don't typically callkamal build create
and it's instead called within a build I'm going to close this one out.kamal build details
to runpack version && pack builder inspect
DoesSince we're not creating a build context like you normally would with Docker there's nothing to actually remove. Is there anything in the Kamal lifecycle though that we at least need a no-op method for this?kamal build remove
need to do anything?