Skip to content

feat: support prepend.Dockerfile* files for multi-stage builds#7071

Merged
rfay merged 15 commits into
ddev:mainfrom
hanoii:dockerfile-stages
Apr 22, 2025
Merged

feat: support prepend.Dockerfile* files for multi-stage builds#7071
rfay merged 15 commits into
ddev:mainfrom
hanoii:dockerfile-stages

Conversation

@hanoii

@hanoii hanoii commented Mar 12, 2025

Copy link
Copy Markdown
Collaborator

The issue

We can customize the default Dockerfile by adding appending stuff to the end (through Dockerfile* variants), and by injecting something before ddev own customizations, but still in between ddev's own Dockerfile (through pre.Dockerfile* variants). But we cannot add anything before everything else, limiting other customization like being able to define other build stages.

How This PR Solves The Issue

It adds a new prepend.Dockerfile* variant that gets added on top of everything before anything added by ddev.

Manual Testing Instructions

  1. create a .ddev/web-build/prepend.Dockerfile.pr-7071 with:
# If we want to use any of the build time environment variables injected by ddev 
# on the prepend.Dockerfile* variants we need to manually declare them to make 
# them available using the ARG instruction.
ARG BASE_IMAGE
FROM $BASE_IMAGE AS build-stage-go

ARG uid
ARG gid

# install go
RUN set -eux; \
    GO_VERSION=$(curl -fsSL "https://go.dev/dl/?mode=json" | jq -r ".[0].version"); \
    AARCH=$(dpkg --print-architecture); \
    wget -q https://go.dev/dl/${GO_VERSION}.linux-${AARCH}.tar.gz -O go.tar.gz; \
    tar -C /usr/local -xzf go.tar.gz; \
    rm go.tar.gz;
  1. Create a .ddev/web-build/Dockerfile.pr-7071 with:
# Copy entire go directory from the build stage defined above.
COPY --from=build-stage-go /usr/local/go /usr/local
  1. Run ddev start and it should build without error.
  2. Check that both files are reported as custom web-build configurations:
Using custom web-build configuration: [
	/tmp/gotest/.ddev/web-build/prepend.Dockerfile.pr-7071
	/tmp/gotest/.ddev/web-build/Dockerfile.pr-7071
] 
  1. Run ddev exec go version and verify the command runs ok and you got the latest go version.

Automated Testing Overview

This verifies that that the all of the variants work by doing a simple build stage that creates a file and then it copies it over. It tests both prepend.Dockerfile and prepend.Dockerfile.*

Release/Deployment Notes

It affects nothing.

Additional context

Most important docs for review: https://ddev--7071.org.readthedocs.build/en/7071/users/extend/customizing-images/#adding-extra-dockerfiles-for-webimage-and-dbimage

This is a follow up for a discord thread: https://discord.com/channels/664580571770388500/1349036904015855636

The reasoning behind this is having a way to, if needed/wanted for any reason, be able to feed to ddev Dockefile build stages. This can help in splitting out big modifications of Dockerfiles into their own stages.

The change to ddev code is super simple actually, just adds any stage.Dockerfile.* that's get added before EVERYTHING in the current Dockerfile, allowing you to do whatever you want there.

While I am still working through a test project that uses this successfully, initial test works just fine and I think you can gather what we can do with this.

i.e.

#ddev-generated - Do not modify this file; your modifications will be overwritten.


### From user Dockerfile /Users/ariel/Sites/work/dockerfile-stage/.ddev/web-build/stage.Dockerfile.pimp-my-shell.ahoy:
#ddev-generated

ARG BASE_IMAGE="scratch"
FROM $BASE_IMAGE AS pimp-my-shell-ahoy

# ahoy
RUN set -eux; \
    AHOY_VERSION=2.2.0; \
    AARCH=$(dpkg --print-architecture); \
    wget -q https://github.com/ahoy-cli/ahoy/releases/download/v${AHOY_VERSION}/ahoy-bin-linux-${AARCH} -O - > /usr/local/bin/ahoy; \
    chmod +x /usr/local/bin/ahoy;


### From user Dockerfile /Users/ariel/Sites/work/dockerfile-stage/.ddev/web-build/stage.Dockerfile.pimpi-my-shell.kitty:
#ddev-generated

ARG BASE_IMAGE="scratch"
FROM $BASE_IMAGE AS pimp-my-shell-kitty

# kitty-terminfo from directoy from their GitHub repo @ master
RUN set -eux; \
    KITTY_VERSION=0.40.0; \
    url="https://github.com/kovidgoyal/kitty/raw/refs/tags/v${KITTY_VERSION}/terminfo/x/xterm-kitty"; \
    mkdir -p /usr/share/terminfo/x; \
    cd /usr/share/terminfo/x; \
    wget -q "${url}";

This is the first part of the .webImageBuild's Dockerfile where it concatenates stage.Dockerfile.pimpi-my-shell.ahoy and stage.Dockerfile.pimpi-my-shell.kitty.

Further below I also have a Dockerfile.pimp-my-shell that does:

COPY --from=pimp-my-shell-ahoy /usr/local/bin/ahoy /usr/local/bin/ahoy
COPY --from=pimp-my-shell-kitty /usr/share/terminfo/x/xterm-kitty /usr/share/terminfo/x/xterm-kitty

Changing something in the ahoy stage, doesn't bust the kitty stage cache. The COPY entries gets busted in order (the first one that get busted busts the others, but the COPY performance is negible.

Note

At first I was trying to support more logic, like adding all of the ARGs or auto detect FROM statements to see if I should add the FROM base image but then I figured I was getting too complex into something that's inherently complex and if one would want to use this, he should cover anything what's required in this part of the dockerfile.

Docs: https://ddev--7071.org.readthedocs.build/en/7071/users/extend/customizing-images/#adding-extra-dockerfiles-for-webimage-and-dbimage

@hanoii hanoii requested a review from a team as a code owner March 12, 2025 18:59
@github-actions

github-actions Bot commented Mar 12, 2025

Copy link
Copy Markdown

@hanoii

hanoii commented Mar 12, 2025

Copy link
Copy Markdown
Collaborator Author

It's obvious that although I am using the base image as provided by ddev, I could have use any other image on each stage.

@hanoii

hanoii commented Mar 12, 2025

Copy link
Copy Markdown
Collaborator Author

And I probably still need to add some notes to the README file, the docs and tests, but before continuing I just wanted your initial "approval" on adding this.

@hanoii

hanoii commented Mar 12, 2025

Copy link
Copy Markdown
Collaborator Author

Added tests and README.txt tweaks

@hanoii hanoii requested a review from a team as a code owner March 12, 2025 22:22
@hanoii

hanoii commented Mar 12, 2025

Copy link
Copy Markdown
Collaborator Author

Added docs, this should be feature complete and ready to review.

Comment thread pkg/ddevapp/config.go Outdated
@hanoii hanoii requested a review from stasadev March 14, 2025 15:25
Comment thread docs/content/users/extend/customizing-images.md Outdated
Comment thread pkg/ddevapp/dotddev_assets/web-build/README.txt Outdated
Comment thread pkg/ddevapp/dotddev_assets/db-build/README.txt Outdated
@hanoii hanoii requested a review from stasadev March 14, 2025 16:05
@hanoii hanoii changed the title feat: support stage.Dockerfile.* files for build stages feat: support stage.Dockerfile* files for build stages Mar 14, 2025
@hanoii hanoii changed the title feat: support stage.Dockerfile* files for build stages feat: support stage.Dockerfile* files for multi-stage builds Mar 14, 2025

@stasadev stasadev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works well, thank you!

I only have a suggestion for your go example.

Comment thread docs/content/users/extend/customizing-images.md Outdated
@hanoii

hanoii commented Mar 21, 2025

Copy link
Copy Markdown
Collaborator Author

shameless bump of this PR to see if it can be considered for next release :)

@rfay

rfay commented Mar 21, 2025

Copy link
Copy Markdown
Member

I hope to get to it, and you have Stas's support, so it's likely. But from community point of view it's not as important as some other things.

@rfay

rfay commented Mar 31, 2025

Copy link
Copy Markdown
Member

We'll try to get this in early in upcoming release cycle.

@stasadev stasadev force-pushed the dockerfile-stages branch from d0cefa1 to ef72d4e Compare April 1, 2025 08:43

@rfay rfay left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your contributions are so very much appreciated in so many places.

But I'm sorry, I'm not very happy with this. I spent some time with it today and it seems to me like it adds complexity to a topic that is already too complex for most people, and primarily does it to support a single user's workflow. We'll leave it open and I'll return to it later.

@rfay

rfay commented Apr 7, 2025

Copy link
Copy Markdown
Member

Could you please restate here exactly what your situation is? We were thinking we should try to solve it in a more generic way, instead of adding a specific feature.

@hanoii

hanoii commented Apr 7, 2025 via email

Copy link
Copy Markdown
Collaborator Author

@rfay

rfay commented Apr 7, 2025

Copy link
Copy Markdown
Member

I know you think of this in terms of stages, but if you can explain again in detail the actual problem you're trying to solve (instead of the means of solving it) we'll brainstorm with you about possible solutions.

@hanoii

hanoii commented Apr 7, 2025

Copy link
Copy Markdown
Collaborator Author

I think it's also in the discord thread linked in the PR description, but adding here the key points:

  • I have https://github.com/hanoii/ddev-pimp-my-shell/blob/main/web-build/Dockerfile.pimp-my-shell
  • It's a longish Dockerfile, it has a lot of different things that gets installed
  • I lock each one to a specific version (One could argue that it's bet to always install the the latest, I preferred this so I can bust cache easily on all projects and that I keep track of the new versions of tools). This gets auto-updated by a recent Github Action. i.e. hanoii/ddev-pimp-my-shell@28935e4
  • These dependencies changes often and between that, ddev updates and me switching through a lot of different projects with different php versions I rebuild the images for for each project a lot of times. It's gotten a bit annoying for my taste so I went to look if I can improve the building time of my add-on.
  • If the dependency that gets updated is very early of the dockerfile all of the other entries need to run, and even more with ddev updates.
  • I figured my best choice was to be able to do it with build stages, so that the docker cache can be used more smartly. For a lot of this I can start with a simple images, download the dependency/install it/dowahtever and then simply copy over the file. If one gets updated, only that "build" stage needs to be rebuild, so now this becomes independent of other dependencies or even ddev images itself. I'd have a lot more options.

@rfay

rfay commented Apr 7, 2025

Copy link
Copy Markdown
Member

Why is it important for it to change all the time?

@hanoii

hanoii commented Apr 7, 2025

Copy link
Copy Markdown
Collaborator Author

Why is it important for it to change all the time?

I guess it's not. I don't have an answer other than keeping it up to date for the sake of it. Same reason I do a brew upgrade so often

@rfay

rfay commented Apr 7, 2025

Copy link
Copy Markdown
Member

Can you say what it is that gets out of date?

Is it possible to make it not have to build when things haven't changed?

@hanoii

hanoii commented Apr 7, 2025 via email

Copy link
Copy Markdown
Collaborator Author

@hanoii

hanoii commented Apr 7, 2025

Copy link
Copy Markdown
Collaborator Author

Maybe prepend. is a better name than stage., but you already have pre.

@rfay rfay force-pushed the dockerfile-stages branch from 4b5d900 to 95e2bf7 Compare April 11, 2025 18:18
@rfay

rfay commented Apr 11, 2025

Copy link
Copy Markdown
Member

Rebased for convenience and just to run the wsl2/docker-ce tests on tb-wsl-14, which was failing before.

@rfay

rfay commented Apr 14, 2025

Copy link
Copy Markdown
Member

OK, I'm willing to go with it. Please use the PR template to provide full information about this PR, including adding explicit (simple) manual testing instructions. Your narrative description is fine justifying the whole thing, but please put it as an addition to the template. After that gets done, Stas and I will do another round of review. It does look fine to me, although I don't know how people will really understand the difference between pre and prepend.

@hanoii

hanoii commented Apr 14, 2025

Copy link
Copy Markdown
Collaborator Author

Done and pushed a minor docs adjustment to match the PR testing instructions ,which I found it clearer.

@stasadev stasadev requested review from rfay and stasadev April 14, 2025 15:52
@rfay

rfay commented Apr 22, 2025

Copy link
Copy Markdown
Member

You'll be happy to know that heredocs are fully supported these days (simpler examples than you have). I'm trying to move to them when I see opportunities. https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/

Just a comment, no need to change anything.

@rfay rfay left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! I hope you like it and others like it!

It does add complexity, and will certainly mean some confusion between pre and prepend, but it also adds power, and hopefully power that helps your situation. Thanks for the contribution, and for scratching your own itch!

The good thing is the docs read well and prepend does come way down there after people have stopped reading.

@stasadev stasadev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good and works as expected. Thank you!

@rfay rfay merged commit 4e6bd6c into ddev:main Apr 22, 2025
@rfay

rfay commented Apr 22, 2025

Copy link
Copy Markdown
Member

Thanks for your contribution and your patience on this!

@hanoii hanoii deleted the dockerfile-stages branch April 30, 2025 15:34
rfay pushed a commit that referenced this pull request May 6, 2025
Co-authored-by: Stanislav Zhuk <stasadev@gmail.com>
@stasadev

stasadev commented Nov 26, 2025

Copy link
Copy Markdown
Member

@hanoii, thank you very much for this contribution!

I reused this technique in:

Multi-stage build was required there, because variable expansion is not supported in COPY --from=, but the workaround is to use FROM image:tag-${expanded_variable} AS build, and then COPY --from=build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants