Deploying Rails app with Kamal - why is my image so big?

programming rails kamal devops docker

I’m currently in process of rewriting app from Python/Flask to Ruby/Rails, and one of changes is in deployment process.

I decided to use containers and Kamal, as it’s upcoming default for Rails. Mind you, I have no prior experience with neither Docker nor deployment tools like Capistrano.

There are many things that I got stuck on (and I plan to write about them maybe later), but today I was trying to solve one problem - images Kamal generated were about 3GB. Whoa, that’s a lot! It takes a long time to build, to upload to registry, to download to server. It’s huge waste of resources!

I had to dig into it, and here’s what I learned:

To find out how big my images really are (and compare before/after some changes), there’s docker images. It lists all my images and their sizes.

But whats inside? I found out about this handy command to show me what’s contained inside the image: docker history registry.example/example-all:latest which shows me something like this:

IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
211b6b39d381   About a minute ago   CMD ["./bin/rails" "server"]                    0B        buildkit.dockerfile.v0
<missing>      About a minute ago   EXPOSE map[3000/tcp:{}]                         0B        buildkit.dockerfile.v0
<missing>      About a minute ago   ENTRYPOINT ["/rails/bin/docker-entrypoint"]     0B        buildkit.dockerfile.v0
<missing>      About a minute ago   USER rails:rails                                0B        buildkit.dockerfile.v0
<missing>      About a minute ago   RUN /bin/sh -c useradd rails --create-home -…   74.9MB    buildkit.dockerfile.v0
<missing>      About a minute ago   COPY /rails /rails # buildkit                   2.7GB     buildkit.dockerfile.v0
<missing>      17 minutes ago       COPY /usr/local/bundle /usr/local/bundle # b…   148MB     buildkit.dockerfile.v0
<missing>      18 minutes ago       RUN /bin/sh -c apt-get update -qq &&     apt…   148MB     buildkit.dockerfile.v0
<missing>      18 minutes ago       ENV RAILS_ENV=production BUNDLE_DEPLOYMENT=1…   0B        buildkit.dockerfile.v0
<missing>      19 minutes ago       WORKDIR /rails                                  0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          CMD ["irb"]                                     0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          RUN /bin/sh -c mkdir -p "$GEM_HOME" && chmod…   0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          ENV PATH=/usr/local/bundle/bin:/usr/local/sb…   0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          ENV BUNDLE_SILENCE_ROOT_WARNING=1 BUNDLE_APP…   0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          ENV GEM_HOME=/usr/local/bundle                  0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          RUN /bin/sh -c set -eux;   savedAptMark="$(a…   58.1MB    buildkit.dockerfile.v0
<missing>      8 weeks ago          ENV RUBY_DOWNLOAD_SHA256=cfb231954b8c241043a…   0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          ENV RUBY_DOWNLOAD_URL=https://cache.ruby-lan…   0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          ENV RUBY_VERSION=3.2.3                          0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
<missing>      8 weeks ago          RUN /bin/sh -c set -eux;  mkdir -p /usr/loca…   45B       buildkit.dockerfile.v0
<missing>      8 weeks ago          RUN /bin/sh -c set -eux;  apt-get update;  a…   46.5MB    buildkit.dockerfile.v0
<missing>      8 weeks ago          /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      8 weeks ago          /bin/sh -c #(nop) ADD file:b86ae1c7ca3586d8f…   74.8MB

Here I saw that my problem lies in COPY rails/ rails/, meaning my app directory is huge.

Why’s that? Here’s another handy command to see that: du --max-depth 1

This shows me size of directories inside my project:

8	./.bundle
108	./test
824388	./.ruby-lsp
176	./config
554380	./vendor
56	./lib
108	./db
125260	./tmp
28	./bin
3034272	./old
24	./.kamal
39508	./.git
8	./.vscode
2300452	./storage
232	./spec
8216	./app
20	./public
427176	./log
4614096	.

The thing is, I started deploying from my laptop, so I have all this files that are not present when deploying from CI/CD (as would be optimal way).

So, we have /storage (with all files), /.ruby-lsp (from editor), /old (thats just some migration data), and /vendor.

I don’t want these in my image! Let’s not include them:

# .dockerignore

old/
*.log

vendor/
tmp/
.ruby-lsp/

After another build, I check my image again, and I got to much nicer ~600MB image.

There might be still some room for improvement, but I’m quite happy with this - my build time is down significantly, my self-hosted registry is not full of useless data, and I learned something new.