agate icon indicating copy to clipboard operation
agate copied to clipboard

Refactor Dockerfile for multi-stage build

Open geraldwuhoo opened this issue 2 years ago • 4 comments

I noticed that when building the Dockerfile, the resultant image had a size of 1.15GB! This did not pass the smell test for a small static Rust binary with an alpine base container.

The current Dockerfile uses a single-stage build, which means that the entire package cache, cargo cache, build cache, etc. are saved in a Docker image layer. This results in an absolutely enormous image.

This PR refactors the Dockerfile to use a multi-stage build, which first uses the rust:alpine Docker image to build the project. The second stage starts from a base alpine container, and only copies the statically compiled binary and the start.sh entrypoint script, which reduces size from 1.15GB to 11.4MB.

I quickly tested this out by using the new Docker image to serve a page, which I could access using amfora. I am not entirely familiar with all the bells and whistles of this project, so I recommend the primary maintainers further test the new Dockerfile before merging.

Passing thoughts

Use of alpine as final base instead of scratch

Just some additional thoughts regarding the Dockerfile -- the only reason I used alpine:latest as the final build stage instead of scratch is due to the start.sh script, which requires a shell to interpret it. However, the start script itself is very simple, being only one command. This command could easily be inlined directly in Dockerfile. The only problem is that CMD command doesn't understand ENV variables. In addition, as scratch does not contain a shell, shell expansion of the environment variable won't work regardless.

If agate itself can be refactored to be configured by environment variables in addition to traditional command line flags, we can use the ENV directive in the Dockerfile to configure the runtime arguments of agate, and skip the startup script entirely. This will allow us to use scratch as the base container, which would save ~5.9MB -- the size of the alpine base container (non-insignificant compared to currently proposed final size of 11.4MB).

This can be addressed in a future PR if there is sufficient interest, and I am glad to implement that as well. I just want to make sure it is actually a desired feature before spending time to implement it -- don't want to spend time writing it just to be shot down due to differing design philosophies.

geraldwuhoo avatar Feb 09 '22 08:02 geraldwuhoo

I can confirm this is a good change. Multi-stage builds will greatly reduce the container size. I'd like to add a few more features, but really can't until this one is merged.

@mbrubeck are you ok with this one as-is?

As for scratch, i generally shy away from it since it makes containers extremely difficult to debug (no exec bash), but it could make sense in this case given the simplicity of agate. One feature i'd like to see is "automatically pulling a gemini website via git and hosting it on startup" (instead of relying on a volume mount). This wouldn't be possible with scratch.

kallisti5 avatar Apr 26 '22 17:04 kallisti5

I am currently waiting for the things from #145 to be modified into this PR.

Johann150 avatar Apr 26 '22 17:04 Johann150

Sorry folks, this slipped PR my mind. I have time to work on this now so hopefully we can get this merged soon :).

geraldwuhoo avatar Apr 27 '22 05:04 geraldwuhoo

Hi,

How is this PR going? I have an alternative aproach at: https://github.com/ldotlopez/agate which builds the container from source (instead of using wget) and multi-stage build.

I would like to open a new PR for my branch if this PR is not merge.

ldotlopez avatar Nov 22 '22 09:11 ldotlopez