recipes
recipes copied to clipboard
build: Optimize Dockerfile
Description
Refactor the Dockerfile for readability, while optimizing for a slimmer final image by reducing the layers and stripping the build dependencies from the production image
Background
I wanted to start contributing to issues, but got side tracked during setup. This is a work in progress as it will require more testing. The image itself relies on external dependencies not included in the Dockerfile (looking at the CI pipelines) and since I haven't got the dev environment up and running yet, unsure which dependencies are solely for building and which are required for production, but err-ed on the side of caution rather than optimization.
Comparison of sizes
| Repository | Arch | Size |
|---|---|---|
| theProf:build/docker-optimization | linux/arm64 | 784MB |
| theProf:build/docker-optimization | linux/amd64 | 157MB |
| TandoorRecipes:feature/vue3 | linux/arm64 | 2.83GB |
| TandoorRecipes:feature/vue3 | linux/amd64 | 237MB |
| theProf:build/docker-optimization | linux/arm64,linux/amd64 | 1.56GB |
| TandoorRecipes:feature/vue3 | linux/arm64,linux/amd64 | 3.78GB |
| ghcr.io/tandoorrecipes/recipes | linux/amd64 | 3.36GB |
Questions
I didn't see anywhere that was tagged with contribution / discussion around DevOps, CI/CD, building, etc, but if I missed it please feel free to point me in the correct direction!
EDIT: Created an Issue for larger discussion that can be found here
hi, thanks, i would like to optizmize the build process. To be honest I don't really have dev and production dependencies well sorted, I just add them to docker whenever something fails to build and the mess has been growing for years.
The vue-3 branch ist the one where all development is happening right now and its probably best to optimize that one from the beginning on so testing can happen gradually while the alpha tests start and nothing on the existing pipeline breaks.
One issue I have had for years is building for the different architectures. With the vue-3 branch I dropped support for arm/v7 because it was a pain to maintain and would just break every few month. Still there are architecture related issues for other arm build as well which sometimes cause the x86 build, which is the most important one, to fail. Properly separating these two would be great but I think it might require different tags on docker hub, not sure.
Feel free to play around and ask if you have any questions, would be great if someone could sort this mess.
To be honest I don't really have dev and production dependencies well sorted, I just add them to docker whenever something fails to build and the mess has been growing for years.
no worries, understood. I'll help get them sorted
The vue-3 branch ist the one where all development is happening right now and its probably best to optimize that one from the beginning
Glad I picked the right branch to base from!
so testing can happen gradually while the alpha tests start and nothing on the existing pipeline breaks
Where is the best place to have discussions around the existing pipelines and how they are used? I'll have some general questions on how/where things are used as I get up to speed and wouldn't want to pollute this PR thread is something like an Issue or Discussion with a label would be better for more QA/advice/back and forth?
One issue I have had for years is building for the different architectures
It can be tough! Especially if you have any dependencies on libc, then you start getting into glibc (common default of Ubuntu for example) vs musl libc (default of Alpine). I'd be happy to help sort out the multi-arch building.
One question I did have is: what is the process for getting the development environment setup from scratch?
I wasn't sure if everything is supposed to be in the Dockerfile after building/running, or if the Dockerfile is just the backend and vue3 needs to be started separately, or if TandoorRecipes/open-tandoor-data needed to be pulled in as well, etc.
If you have any sort of "minimum commands to run" to get started, it would be greatly appreciated.
One question I did have is: what is the process for getting the development environment setup from scratch?
clone the repo, install yarn/pip requirements, start the yarn dev server and start the django dev server, run migrations. That should be all thats needed to get a hot reloading dev setup to work.
discussion
feel free to open an issue, I get notified so I will respond there. Alternatively I could offer a quick discord call to explain stuff.
Generally speaking the whole system is just "what worked" for the last years, I have tried a few times to optimize for different things but overall there isn't much reason for any particular decision besides that it worked, so feel free to change whatever you want, it just needs to work :)
Alternatively I could offer a quick discord call to explain stuff
That is a very kind offer, I will happily take you up on this!
feel free to open an issue
Also did so here (and added link in the PR description), but a realtime chat may be more efficient upfront
Great work @theProf <3
Did a quick review with some other newish Docker syntax, nice use of the new
--linkalready!Feel free to ping for any questions or a final review when you are done. I now just did a quick review in the Github interface no local checkout.
It would IMO also be nice to setup an unprivilged user and switching to that. Quick copy paste to setup a 1000:1000 user and group without
useraddoradduser(and remembering the different syntaxes hehe):FROM alpine as builder RUN <<PASSWD cat > /etc_passwd && <<GROUP cat > /etc_group app:x:1000:1000:app:/: PASSWD app:x:1000:app GROUP FROM alpine COPY --from=builder /etc_group /etc/group COPY --from=builder /etc_passwd /etc/passwd USER app
Thanks for the review! I'm glad I kept this as a Draft PR as I feel it best to split it. I've realized there are 2 goals and want to try my best not to mix them (as it will end up being a large and difficult to review PR):
- Update and organize the Dockerfile to build a production ready image with clarity on dependencies and build steps
- Optimize the build
I mention as I'm 100% on board for things like RUN mount caches (have it in a local branch), but that falls under 2 and will have impacts to the build pipeline as well (to actually utilize the cache). The reason I want to focus on 1 is to gain clarity on things like: https://github.com/TandoorRecipes/recipes/pull/3606/files#r2033544477
I would add .git in the .dockerignore file instead of removing it
I thought the same! I then found out there may be git submodules used by the pipelines such as here. And that's the kind of thing I'd like to move into the Dockerfile as a source of truth on all dependencies and build steps.
Once the Dockerfile is organized and a reliable source of truth, it will be much easier to add optimizations with confidence.
I'm glad I kept this as a Draft PR as I feel it best to split it.
Yes I think that is a good call, might still be nice to keep the RUN --mount stuff part of the Dockerfile updates, although the pipeline isn't using it fully (since ephemeral) it won't harm and would increase local compile speeds significantly.
Then the Dockerfile is a given and the second PR could focus fully on optimizing the pipelines. That would also give a nice insight in the performance gain.
Update and organize the Dockerfile to build a production ready image with clarity on dependencies and build steps
IMO the non root running of the image would be part of this. At this moment you can't run this image on Kubernetes where Pod Security is enforced. Let me know if you need any contribution in that regard, kinda my dayjob haha.
I thought the same! I then found out there may be git submodules used by the pipelines such as here. And that's the kind of thing I'd like to move into the Dockerfile as a source of truth on all dependencies and build steps.
Interesting one, it seems that there previously where two tags (normal and open-data), for the 2.0.0 release the open-data workflow has been removed: https://github.com/TandoorRecipes/recipes/tree/feature/vue3/.github/workflows
Maybe @vabene1111 has something else in mind to import the open data (maybe in the new interface?) instead of releasing two containers.
Otherwise I would make it an ARG OPENDATA and do some actions within the Dockerfile based on the buildarg (as the last layer so it won't invalidate layers). Then it can easily be one workflow with two builds where the open-data build can reuse all the layers of the normal image.
thanks, I definitely already have no idea what you are talking about but it seems you two understand each other 😂
regarding the open data/submodules: tandoor supports plugins. This is not really documented and not used by anyone other than me to add the open data project to the hosted instance and to add some hosted specific code to that. In the future I would like the abiliy to install these plugins at runtime because its annoying to keep multiple pipelines working for them, thats why I removed them for the tandoor 2 branch. The problem is that I am not yet sure that I will be able to do that. Ideally there would be an interface where you add a git url, clone the repo, yarn build runs in the background and then the new modules are available. At the same time during development I want them to be in the same structure to make it faster.
I would say for now leave this out, I will need to check if runtime installing of plugins works and we will see. Obviously if any of you are interested in building that support (or at least making sure the required dependencies will be available in the image) that would be great.
One more information regarding the volumes: The only reason I use a mix of volumes and bind mounts is to provide the default nginx config. I have always kept nginx seperate from the base image because I thought people should have the freedom to choose their webserver and because I thought bundling nginx might be a bad idea. That said it has caused many people trouble over the years and I think there are many instances running without the nginx which is not good and can even break things from time to time, so I would not be opposed to including nginx directly in the image, depending on what you two think is best practice. I am happy to get your input on this matter
IMO the non root running of the image would be part of this. At this moment you can't run this image on Kubernetes where Pod Security is enforced.
I'm in that situation too, trying (and so far failing) to run everything under the restricted Pod Security Standard. So I'd very much appreciate the non-root user.
@wilmardo actually, a user account doesn't even need to exist in the container image. USER 10000:10000 is enough. That's how we run our containers at work and it's not an issue unless the application tries to look up the username it's running under.
Thank you @theProf ! :smile: I think those are good improvements to the clarity of the container build process.
Sorry I haven't been responsive, had some personal life going on. Great to see all the activity and feedback!
I'll take a crack at these (and creating a checklist to split things up) tomorrow morning.
Took a bit longer as I had to get reacquainted with the code. Only pushed a rebase from feature/vue3. But putting update in the issue
@theProf this has probably been closed by accident when the branch got deleted. Would you mind reopening?
@theProf this has probably been closed by accident when the branch got deleted. Would you mind reopening?
thanks for the nudge. initially thought it was closed out intentionally. Have had some personal life events so haven't been so active. I'll open this back up and likely stack some changes with @wilmardo PRs ref