-
Notifications
You must be signed in to change notification settings - Fork 75
RFC: Support Dockerfiles #173
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
Changes from 16 commits
4f73fe4
ef2b230
e21b2af
a1810ca
82cbe67
a9daed9
3534dc2
05dc2d3
f3e84c6
1e813b9
ba9ebd7
58ed531
ebf6b2a
22f0ef4
084822e
b3b38ea
6eed4b2
fde7b84
f643f8b
3a37d54
f513c24
884eaef
cd9471f
a2c06a5
2935c8a
01786f8
10a02f2
2c7bb58
2c6ca72
aa74ace
10b6c9f
1cc2789
a2282f1
9ba0bc2
9533642
bfc5781
68773ac
fefd338
74767a0
3b73177
4abb253
f8e7bfa
3a26570
73de183
9325a13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| # Meta | ||
| [meta]: #meta | ||
| - Name: Support Dockerfiles | ||
| - Start Date: 2021-06-30 | ||
| - Author(s): sclevine | ||
| - RFC Pull Request: (leave blank) | ||
| - CNB Pull Request: (leave blank) | ||
| - CNB Issue: (leave blank) | ||
| - Supersedes: [RFC0069](https://github.com/buildpacks/rfcs/blob/main/text/0069-stack-buildpacks.md), [RFC#167](https://github.com/buildpacks/rfcs/pull/167) | ||
| - Depends on: [RFC#172](https://github.com/buildpacks/rfcs/pull/172) | ||
|
|
||
| # Summary | ||
| [summary]: #summary | ||
|
|
||
| This RFC introduces functionality for customizing base images, as an alternative to stackpacks ([RFC0069](https://github.com/buildpacks/rfcs/blob/main/text/0069-stack-buildpacks.md)). | ||
|
|
||
| # Motivation | ||
| [motivation]: #motivation | ||
|
|
||
| [RFC0069](https://github.com/buildpacks/rfcs/blob/main/text/0069-stack-buildpacks.md) introduces complexity by defining an API that allows buildpacks to modify base images. To avoid this complexity, we could rely on generated Dockerfiles for base image manipulation. This would simplify the original proposal by, e.g., only requiring a copy of the buildpack on the build-time base image. | ||
|
|
||
| # What it is | ||
| [what-it-is]: #what-it-is | ||
|
|
||
| This RFC proposes that we replace stackpacks with dynamically-generated build-time and runtime Dockerfiles that act as pre-build hooks. | ||
| These hooks would participate in detection and execute before the buildpack build process. | ||
|
|
||
| For a given application, a build that uses hooks could be optimized by creating a more narrowly-scoped builder that does not contain hooks. | ||
|
|
||
| # How it Works | ||
| [how-it-works]: #how-it-works | ||
|
|
||
| Note: kaniko, buildah, BuildKit, or the original Docker daemon may be used to apply Dockerfiles at the platform's discretion. | ||
|
|
||
| ### Dynamically-applied Dockerfiles | ||
|
|
||
| A builder image may include any number of "hook" directories in `/cnb/hooks/`. | ||
|
natalieparellano marked this conversation as resolved.
Outdated
|
||
|
|
||
| Hooks are similar to buildpacks: they have a `/bin/build` and `/bin/detect` executable. | ||
| However, instead of a `buildpack.toml` file, hooks have a `hook.toml` file: | ||
| ```toml | ||
| api = "<buildpack API version>" | ||
|
jkutner marked this conversation as resolved.
|
||
|
|
||
| [hook] | ||
| id = "<hook ID>" | ||
| name = "<hook name>" | ||
| version = "<hook version>" | ||
| homepage = "<hook homepage>" | ||
| description = "<hook description>" | ||
| keywords = [ "<string>" ] | ||
|
|
||
| [[hook.licenses]] | ||
| type = "<string>" | ||
| uri = "<uri>" | ||
| ``` | ||
|
|
||
| Hooks may be packaged and examined similar to buildpacks, but with analogous `pack hook` subcommands. | ||
|
|
||
| Other pack CLI commands, such as `pack builder create`, will be extended to include support for hooks. | ||
|
|
||
| Unlike buildpacks, | ||
| - Hooks must not be included in a meta-buildpacks | ||
| - Hooks must not have `order`/`group` definitions in `hook.toml` | ||
|
|
||
| Hooks participate in the buildpack detection process, with the same interface for `/bin/detect`. | ||
|
jromero marked this conversation as resolved.
Outdated
|
||
| However, | ||
| - `/bin/detect` is optional for hooks, and they are assumed to pass detection when it is not present. Just like with buildpacks, a /bin/detect that exits with a 0 exit code passes detection, and fails otherwise. | ||
|
sclevine marked this conversation as resolved.
Outdated
|
||
| - Hooks may only output `provides` entries to the build plan. They must not output `requires`. | ||
| - Hooks must all proceed regular buildpacks in `order` definitions (e.g., in `builder.toml`). | ||
|
natalieparellano marked this conversation as resolved.
Outdated
|
||
| - Hooks are always `optional`. | ||
|
|
||
| Hooks generate Dockerfiles before the regular buildpack build phase. | ||
| To generate these Dockerfiles, the lifecycle executes the hook's `/bin/build` executable with the same interface as regular buildpacks. | ||
|
jromero marked this conversation as resolved.
Outdated
|
||
| However, | ||
| - Hooks `/bin/build` must not write to the app directory. | ||
|
jkutner marked this conversation as resolved.
Outdated
|
||
| - Hooks `/bin/build` may be executed in parallel. | ||
|
natalieparellano marked this conversation as resolved.
Outdated
|
||
| - Hooks `<layers>` directory is replaced by an `<output>` directory. | ||
|
jkutner marked this conversation as resolved.
Outdated
|
||
| - If a hook is missing `/bin/build`, the hook root is treated as a pre-populated `<output>` directory. | ||
|
|
||
| After `/bin/build` executes, the `<output>` directory may contain | ||
|
sclevine marked this conversation as resolved.
Outdated
|
||
| - `build.toml`, with the same contents as a normal buildpack's `build.toml`, but | ||
| - With an additional `args` table array with `name` and `value` fields that are provided as build args to `build.Dockerfile` or `Dockerfile` | ||
|
natalieparellano marked this conversation as resolved.
Outdated
|
||
| - `launch.toml`, with the same contents as a normal buildpack's `launch.toml`, but | ||
|
natalieparellano marked this conversation as resolved.
Outdated
|
||
| - Without the `processes` table array | ||
| - Without the `slices` table array | ||
|
natalieparellano marked this conversation as resolved.
Outdated
|
||
| - With an additional `args` table array with `name` and `value` fields that are provided as build args to `run.Dockerfile` or `Dockerfile` | ||
|
|
||
| - Either `Dockerfile` or either or both of `build.Dockerfile` and `run.Dockerfile` | ||
|
natalieparellano marked this conversation as resolved.
Outdated
|
||
|
|
||
| Support for other instruction formats, e.g., LLB JSON files, could be added in the future. | ||
|
|
||
| `build.Dockerfile`, `run.Dockerfile`, and `Dockerfile` target the builder image, runtime base image, or both base images, respectively. | ||
|
natalieparellano marked this conversation as resolved.
Outdated
|
||
|
|
||
| If no Dockerfiles are present, `/bin/build` may still consume build plan entries and add metadata to `build.toml`/`launch.toml`. | ||
|
natalieparellano marked this conversation as resolved.
Outdated
sclevine marked this conversation as resolved.
Outdated
|
||
|
|
||
| Dockerfiles are applied to their corresponding base images after all hooks are executed and before any regular buildpacks are executed. | ||
|
jromero marked this conversation as resolved.
Outdated
|
||
| Dockerfiles are applied in the order determined during buildpack detection. | ||
|
natalieparellano marked this conversation as resolved.
Outdated
sclevine marked this conversation as resolved.
Outdated
|
||
|
|
||
| All Dockerfiles are provided with `base_image` and `build_id` args. | ||
|
sclevine marked this conversation as resolved.
|
||
| The `base_image` arg allows the Dockerfile to reference the original base image. | ||
| The `build_id` arg allows the Dockerfile to invalidate the cache after a certain layer and must be defaulted to `0`. The executor of the Dockerfile will provide the `build_id` as a UUID (this eliminates the need to track this variable). | ||
|
natalieparellano marked this conversation as resolved.
Outdated
natalieparellano marked this conversation as resolved.
Outdated
|
||
| When the `$build_id` arg is referenced in a `RUN` instruction, all subsequent layers will be rebuilt on the next build (as the value will change). | ||
|
|
||
| Build args specified in `build.toml` are provided to `build.Dockerfile` or `Dockerfile` (when applied to the build-time base image). | ||
| Build args specified in `launch.toml` are provided to `run.Dockerfile` or `Dockerfile` (when applied to the runtime base image). | ||
|
|
||
| A runtime base image may indicate that it preserves ABI compatibility by adding the label `io.buildpacks.rebasable=true`. In the case of builder-specified Dockerfiles, `io.buildpacks.rebasable=false` is set automatically on the base image before a runtime Dockerfile is applied and must be explicitly set to `true` if desired. If multiple Dockerfiles are applied, all must set `io.buildpacks.rebasable=true` for the final value to be `true`. Rebasing an app without this label set to `true` requires passing a new `--force` flag to `pack rebase`. | ||
|
natalieparellano marked this conversation as resolved.
Outdated
natalieparellano marked this conversation as resolved.
Outdated
|
||
|
|
||
| Finally, base images may be statically labeled with any number of `provides` that are treated as build plan entries. | ||
|
sclevine marked this conversation as resolved.
Outdated
|
||
| These `provides` may contain fields other than `name`, which, when mismatched with `requires`, mark the entry as `unmet` by the stack. | ||
|
sclevine marked this conversation as resolved.
Outdated
|
||
| This is important to ensure that: | ||
| - Rebasing always remains an option for end users. | ||
| - Buildpacks do not become dependent on hooks. | ||
| - Builds can be time-optimized by creating base images ahead of time. | ||
|
|
||
| NOTE: the above can be accomplished by a hook with a no-op `/bin/build` -- do we really need this? | ||
|
|
||
| #### Example: App-specified Dockerfile Hook | ||
|
|
||
| This example hook would allow an app to provide runtime and build-time base image extensions as "run.Dockerfile" and "build.Dockerfile." | ||
|
natalieparellano marked this conversation as resolved.
Outdated
sclevine marked this conversation as resolved.
Outdated
|
||
| The app developer can decide whether the extensions are rebasable. | ||
|
|
||
| ##### `/cnb/hooks/com.example.apphook/bin/build` | ||
| ``` | ||
| #!/bin/sh | ||
| [ -f build.Dockerfile ] && cp build.Dockerfile "$1/" | ||
|
natalieparellano marked this conversation as resolved.
|
||
| [ -f run.Dockerfile ] && cp run.Dockerfile "$1/" | ||
| ``` | ||
|
Comment on lines
+120
to
+125
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given we are allowing static Dockerfiles in the case where there is no
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this example is copying the Dockerfiles from the app directory to the output directory |
||
|
|
||
| #### Example: RPM Dockerfile Hook (app-based) | ||
|
|
||
| This example hook would allow a builder to install RPMs for each language runtime, based on the app directory. | ||
|
sclevine marked this conversation as resolved.
Outdated
|
||
|
|
||
| Note: The Dockerfiles referenced must disable rebasing, and build times will be slower compared to buildpack-provided runtimes. | ||
|
natalieparellano marked this conversation as resolved.
|
||
|
|
||
| ##### `/cnb/hooks/com.example.rpmhook/bin/build` | ||
| ``` | ||
| #!/bin/sh | ||
| [ -f Gemfile.lock ] && cp "$CNB_BUILDPACK_DIR/Dockerfile-ruby" "$1/Dockerfile" | ||
| [ -f package.json ] && cp "$CNB_BUILDPACK_DIR/Dockerfile-node" "$1/Dockerfile" | ||
| ``` | ||
|
|
||
|
|
||
| ### Dockerfiles for Base Images | ||
|
|
||
| The same Dockerfile format may be used to create new base images or modify existing base images outside of the app build process (e.g., before creating a builder). Any specified labels override existing values. | ||
|
|
||
| Dockerfiles that are used to create a base image must create a `/cnb/image/genpkgs` executable that outputs a [CycloneDX](https://cyclonedx.org)-formatted list of packages in the image with PURL IDs when invoked. This executable is executed after all Dockerfiles are applied, and the output replaces the label `io.buildpacks.sbom`. This label doubles as a Software Bill-of-Materials for the base image. In the future, this label will serve as a starting point for the application SBoM. | ||
|
natalieparellano marked this conversation as resolved.
Outdated
natalieparellano marked this conversation as resolved.
Outdated
sclevine marked this conversation as resolved.
Outdated
|
||
|
|
||
| ### Example Dockerfiles | ||
|
|
||
| Dockerfile used to create a runtime base image: | ||
|
|
||
| ``` | ||
| ARG base_image | ||
| FROM ${base_image} | ||
| ARG build_id=0 | ||
|
sclevine marked this conversation as resolved.
|
||
|
|
||
| LABEL io.buildpacks.image.distro=ubuntu | ||
| LABEL io.buildpacks.image.version=18.04 | ||
| LABEL io.buildpacks.rebasable=true | ||
|
sclevine marked this conversation as resolved.
|
||
|
|
||
| ENV CNB_USER_ID=1234 | ||
| ENV CNB_GROUP_ID=1235 | ||
|
|
||
| RUN groupadd cnb --gid ${CNB_GROUP_ID} && \ | ||
| useradd --uid ${CNB_USER_ID} --gid ${CNB_GROUP_ID} -m -s /bin/bash cnb | ||
|
|
||
| USER ${CNB_USER_ID}:${CNB_GROUP_ID} | ||
|
|
||
| COPY genpkgs /cnb/image/genpkgs | ||
|
sclevine marked this conversation as resolved.
Outdated
|
||
| ``` | ||
|
|
||
| `run.Dockerfile` for use with the example `app.run.Dockerfile.out` hook that always installs the latest version of curl: | ||
| ``` | ||
| ARG base_image | ||
| FROM ${base_image} | ||
| ARG build_id=0 | ||
|
|
||
| RUN echo ${build_id} | ||
|
|
||
| RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* | ||
| ``` | ||
| (note: this Dockerfile disables rebasing, as OS package installation is not rebasable) | ||
|
|
||
| `run.Dockerfile` for use with the example `app.run.Dockerfile.out` hook that installs a special package to /opt: | ||
| ``` | ||
| ARG base_image | ||
| FROM ${base_image} | ||
| ARG build_id=0 | ||
|
|
||
| LABEL io.buildpacks.rebasable=true | ||
|
|
||
| RUN curl -L https://example.com/mypkg-install | sh # installs to /opt | ||
| ``` | ||
| (note: rebasing is explicitly allowed because only a single directory in /opt is created) | ||
|
|
||
|
|
||
| # Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unfortunate that this proposal introduces a new concept (hooks) that a builder-image maintainer needs to learn; especially when this and #172 begin to simplify and remove some concepts. I also
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is subjective, can you qualify this statement with some details or an example? I would argue that you find the concept of a "hook" (or pre/post script, before/after, wrapper, etc..) in tons of places in the computer world. The general concept is something many sys-admins and dev's have probably already seen. I agree it adds some complexity because to use a hook, you need to understand the expected output format & what information you have available to generate your output (as well as basic scripting). At the same time, it's optional. You don't need any hooks to generate a builder, so a builder-builder doesn't really need to know this unless they are trying to do more customizations, which you could say is a more advanced task. My $0.02 only, but it seems like the ratio of complexity added versus additional functionality for users is quite reasonable.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is what I believe makes it not intuitive:
These are not very difficult things to learn, but it's yet another thing to learn (which is a shame given that we're looking to remove things like the stack and mixin to make buildpacks easier to grok). By comparison to your examples, a pre/post script usually stems from the same construct as the thing it's wrapping around (another executable). Similarly, the pre/post proposal for project.toml adds buildpacks before/after buildpacks. These things are all homogenous. But here, we have a concept that introduces a new mechanism into the existing "order". So now we have heterogeneous things running in some order. I think that's what makes it difficult to reason about.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, so there seem to be some specifics in the RFC that you think need to be clarified (I agree with some of these btw). IMHO, that seems like something that can be addressed and not a fundamental flaw in the way this is working. Is that your thought as well? I will have to respectfully disagree on the "hook" as a concept though. There's is a fairly basic lifecycle that buildpacks flow through, you're talking about being able to inject some actions at a couple of points in that lifecycle. This happens all the time in software, they even created AOP to formalize the concept. It happens in software systems as well, the venerable HTTP Servlet Filter in the Java world is an example. As long as we document and have a picture of the lifecycle & indicate where the hooks execute (which is why I agree we need to clarify that point), then we should be fine. I also strongly believe the complexity we are adding is more than worth it with the functionality this is adding & the number of problems this can solve.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't consider this a substitute for a tight set of constructs where people don't need to read and learn about them for each thing they need to do.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. No disagreement there. Learning nothing is simpler than having to learn something, even if it's simple. What I'm left trying to understand is this. If your bullet list of concerns were addressed then would the complexity that this RFC adds be OK? or is it your opinion that this RFC is fundamentally too complex and not something we should add?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I'm generally in favor of this RFC. I'm only trying to push us to find an abstraction over the hook mechanism that makes it's easier for users to understand (especially buildpack authors and builder-image owners). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm also in favour of this RFC. Nevertheless, It is still very hard (as reported too by Joe) to understand different very important points such as :
Remark: That should be great to have a simple text workflow explaining how hooks will be integrated within the existing buildpacks phases (e.g: DETECTION --> ANALYSIS --> BUILD --> EXPORT ==> DETECTION --> ANALYSIS --> HOOK --> BUILD --> EXPORT)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These are covered in the RFC. Can you be more specific about what details you feel are missing?
Do you mean stored as in cached? Caching strategy would be up to the platform to decide.
The Dockerfile would run as root on the base image and produce a new image. No other changes to file permissions would occur.
As specified, buildpacks could create Dockerfiles in their ARGV[1] directory. They could copy them from the app directory, as described in the example, but that's not built-in to the API.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think you mean hooks? |
||
|
|
||
| - Involves breaking changes. | ||
|
|
||
| # Alternatives | ||
| [alternatives]: #alternatives | ||
|
|
||
| - Stackpacks | ||
| - Previous versions of this RFC that don't interact with the buildpack API. | ||
|
|
||
| # Unresolved Questions | ||
| [unresolved-questions]: #unresolved-questions | ||
|
sclevine marked this conversation as resolved.
|
||
|
|
||
| - Should `genpkgs` be part of this proposal? Opinion: Yes, otherwise it's difficult to maintain a valid SBoM. | ||
|
sclevine marked this conversation as resolved.
Outdated
|
||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this weaken the security stance of buildpacks? (My opinion: yes) You can literally do anything in a Dockerfile. I feel like stack packs, while maybe not the answer, did put more controls around what you could do. The stack packs were buildpacks, so presumably (IDK exactly as it wasn't implemented), an Operations team could control which stack packs are available to their users, thereby limiting what their users could install or modify to some degree. There's also a stronger guarantee that a stack pack would produce a legitimate BOM, since Operations teams can control what stack packs are available (and audit them). With this proposal, someone could make a Dockerfile that installs something malicious and set
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't have any answers here, but this seems like a fairly important point to linger on.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this concern is pretty well addressed by including the hooks on the builder. If the app-specified Dockerfile hook (above example) is not present, then the developer cannot provide a Dockerfile. And I suppose that trusting hook authors to provide a valid BOM is the same as trusting buildpack authors to provide a valid BOM. In the case of the app-specified Dockerfile, the hook author could apply some label to the image so that consumers would know to approach the BOM with some suspicion. I wonder if there's value in having that spec'd ('io.buildpacks.extended'?). It's just a thought.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of a label to easily identify images that have had any hooks executed.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@natalieparellano I don't really understand how this helps. By the time it is invoked, the hook author could have replaced
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @samj1912 and I have recently been discussing having the platform inject
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
How would that work? I don't think a mount would work, since the hook could discover and overwrite it. Since the hook could do anything, I'm not even sure running a program like That said - these hooks are likely authored and for sure approved/distributed by the stack author. Just because
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's basically what we're proposing - please see https://github.com/buildpacks/rfcs/pull/173/files#r794608075 for the download I got from @samj1912 and @sclevine . |
||
| # Spec. Changes (OPTIONAL) | ||
| [spec-changes]: #spec-changes | ||
|
|
||
| This RFC requires extensive changes to all specifications. | ||
|
jkutner marked this conversation as resolved.
natalieparellano marked this conversation as resolved.
sclevine marked this conversation as resolved.
|
||
Uh oh!
There was an error while loading. Please reload this page.