gtag('config', 'G-0PFHD683JR');
Price Prediction

Improving Docker is more than just something and accomplishes

This article is part of a series of publications where I will walk in every line of the Dockerfile bars and explain best practices and improvements.

Docker images can be improved in different ways, including, but not limited to, reduce image size, improve performance performance, safety, best practices, and application improvements. In the first article, I will only touch on improving the size of the image and explaining the reason for the importance of this.

Why to improve the image size?

As in every software development process, each developer will include his causes that he wants to make Docker build faster. I will include the most important reasons for me.

Benate and faster publishing operations

The smaller images are faster for construction because fewer files and layers must be processed. This improves developer productivity, especially during repetitive development sessions. The smaller pictures takes less time to pay to the record and withdraw from it during publishing operations. This is especially important in CI/CD pipelines where containers are built and spread frequently.

Decreased storage costs and the use of the network frequency

Smaller photos consume less storage in container records, local development machines and production servers. This reduces infrastructure costs, especially for publication on a large scale. Smaller images use a lesser -width domain of frequency when they are transferred between servers, especially when you build images locally or in CI/CD pipelines and push them to the record.

“We spent $ 3.2 million on the cloud in 2022 … We stand to save about 7 million dollars at the expenses of the server over five years of our cloud exit.” David Haname Hanson – O World

Improving performance and security

Smaller images require lower resources (for example, the CPU, RAM) to download and operate, and improve the overall performance of barefoot applications. Starting times faster means that your services are more ready quickly, which is very important for high delivery and availability. Minimum basic images such as alpine or debian-slim It contains a fewer pre -installed packages, which reduces the risk of exploiting unavoidable or unnecessary software.

Besides all mentioned above, removing unnecessary files and tools reduces deviations when diagnosing problems and leads to a better ability to maintain and reduce technical debts.

Docker photos

For different parameters of the image, including the size, you can either take a look on a Docker desktop or play docker images Order at the station.

➜ docker images
REPOSITORY        TAG       IMAGE ID       CREATED        SIZE
kamal-dashboard   latest    673737b771cd   2 days ago     619MB
kamal-proxy       latest    5f6cd8983746   6 weeks ago    115MB
docs-server       latest    a810244e3d88   6 weeks ago    1.18GB
busybox           latest    63cd0d5fb10d   3 months ago   4.04MB
postgres          latest    6c9aa6ecd71d   3 months ago   456MB
postgres          16.4      ced3ad69d60c   3 months ago   453MB

Knowing the image size does not give you the full image. You don’t know what is inside the image, the number of layers it has, or how big each layer is. A Docker’s image layer It is only reading, The non -changing file system This is made up of a Docker image. Each layer represents a set of changes made on the image file system, such as adding files, editing configurations or installing programs.

Docker’s pictures are gradually designed, layer after another, and each layer corresponds to instructions in Dockerfile. For photo layers, you can play docker history He orders.

➜ docker history kamal-dashboard:latest
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
673737b771cd   4 days ago    CMD ["./bin/thrust" "./bin/rails" "server"]     0B        buildkit.dockerfile.v0
      4 days ago    EXPOSE map[80/tcp:{}]                           0B        buildkit.dockerfile.v0
      4 days ago    ENTRYPOINT ["/rails/bin/docker-entrypoint"]     0B        buildkit.dockerfile.v0
      4 days ago    USER 1000:1000                                  0B        buildkit.dockerfile.v0
      4 days ago    RUN /bin/sh -c groupadd --system --gid 1000 …   54MB      buildkit.dockerfile.v0
      4 days ago    COPY /rails /rails # buildkit                   56.2MB    buildkit.dockerfile.v0
      4 days ago    COPY /usr/local/bundle /usr/local/bundle # b…   153MB     buildkit.dockerfile.v0
      4 days ago    ENV RAILS_ENV=production BUNDLE_DEPLOYMENT=1…   0B        buildkit.dockerfile.v0
      4 days ago    RUN /bin/sh -c apt-get update -qq &&     apt…   137MB     buildkit.dockerfile.v0
      4 days ago    WORKDIR /rails                                  0B        buildkit.dockerfile.v0
      3 weeks ago   CMD ["irb"]                                     0B        buildkit.dockerfile.v0
      3 weeks ago   RUN /bin/sh -c set -eux;  mkdir "$GEM_HOME";…   0B        buildkit.dockerfile.v0
      3 weeks ago   ENV PATH=/usr/local/bundle/bin:/usr/local/sb…   0B        buildkit.dockerfile.v0
      3 weeks ago   ENV BUNDLE_SILENCE_ROOT_WARNING=1 BUNDLE_APP…   0B        buildkit.dockerfile.v0
      3 weeks ago   ENV GEM_HOME=/usr/local/bundle                  0B        buildkit.dockerfile.v0
      3 weeks ago   RUN /bin/sh -c set -eux;   savedAptMark="$(a…   78.1MB    buildkit.dockerfile.v0
      3 weeks ago   ENV RUBY_DOWNLOAD_SHA256=018d59ffb52be3c0a6d…   0B        buildkit.dockerfile.v0
      3 weeks ago   ENV RUBY_DOWNLOAD_URL=https://cache.ruby-lan…   0B        buildkit.dockerfile.v0
      3 weeks ago   ENV RUBY_VERSION=3.4.1                          0B        buildkit.dockerfile.v0
      3 weeks ago   ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
      3 weeks ago   RUN /bin/sh -c set -eux;  mkdir -p /usr/loca…   19B       buildkit.dockerfile.v0
      3 weeks ago   RUN /bin/sh -c set -eux;  apt-get update;  a…   43.9MB    buildkit.dockerfile.v0
      3 weeks ago   # debian.sh --arch 'arm64' out/ 'bookworm' '…   97.2MB    debuerreotype 0.15

Since I have already presented a theory about pictures and classes, it is time to explore Dockerfile. Starting with bars 7.1, Dockerfile It is created with the new Rails app. Below is an example of its shape.

# syntax=docker/dockerfile:1
# check=error=true

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.1
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

# Rails app lives here
WORKDIR /rails

# Install base packages
# Replace libpq-dev with sqlite3 if using SQLite, or libmysqlclient-dev if using MySQL
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development"

# Throw-away build stage to reduce size of final image
FROM base AS build

# Install packages needed to build gems
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential curl git pkg-config libyaml-dev && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
    bundle exec bootsnap precompile --gemfile

# Copy application code
COPY . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile

# Final stage for app image
FROM base

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
    useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
    chown -R rails:rails db log storage tmp
USER 1000:1000

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]

Below I will present a list of the methods and rules that are applied to Dockerfile Above to make the final image size effective.

Improving packages

I’m sure you only keep the necessary programs on your local development machine. The same thing should be applied to Docker. In the examples below, I will constantly increase the Dockerfile extracted from the Rails Dockerfile above. I will refer to it as creative Dockerfile Issue.

Rule No. 1: Use the minimum basic images

FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

The main image is the starting point for Dockerfile. It is the image used to create the container. The main image is the first layer in DockerfileAnd it is the only layer that is not created by Dockerfile itself.

The basic image is determined with FROM It is followed by the name of the image and the brand. The mark is optional, and if not determined, latest The mark is used. The basic image can be any image available on Docker Hub or any other record.

in Dockerfile About, we use ruby Image with 3.4.1-slim sign. the ruby The image is the official Ruby image available on Docker Hub. the 3.4.1-slim The brand is a small version of the Ruby image based on debian-slim image. while debian-slim The image is the minimal release of the improved Debian Linux image. Look at the table below to get an idea about a smaller range slim Photo.

➜ docker images --filter "reference=ruby"
REPOSITORY   TAG              IMAGE ID       CREATED      SIZE
ruby         3.4.1-slim       0bf957e453fd   5 days ago   219MB
ruby         3.4.1-alpine     cf9b1b8d4a0c   5 days ago   99.1MB
ruby         3.4.1-bookworm   1e77081540c0   5 days ago   1.01GB

As of January 2024, the current Debian version is called Bookworm And the other is Polse.

219 MB instead of 1 GB – a big difference. But what if alpine The image is smaller? the alpine The image depends on Alpine Linux distribution, which is a very lightweight Linux distribution that is improved for size and safety. Use Alpine Mountains musl Library (instead of glibc) And busybox (Compressed set of UNIX tools) instead of GNU counterparts. While it is technically possible to use alpine A picture to run bars, I will not cover them in this article.

Rule No. 2: Reducing layers

RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

all RUNand COPY and FROM Instructions in Dockerfile It creates a new layer. The higher the number of layers you have, the greater the size of the image. That is why the best practices are to combine multiple orders in one RUN directions. To clarify this point, let’s take a look at the example below.

# syntax=docker/dockerfile:1
# check=error=true

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.1
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

RUN apt-get update -qq
RUN apt-get install --no-install-recommends -y curl
RUN apt-get install --no-install-recommends -y libjemalloc2
RUN apt-get install --no-install-recommends -y libvips
RUN apt-get install --no-install-recommends -y libpq-dev
RUN rm -rf /var/lib/apt/lists /var/cache/apt/archives

CMD ["echo", "Whalecome!"]

I have divided RUN Instructions in multiple lines, which makes it more capable of human reading. But how will this affect the size of the image? Let’s build the picture and check this.

➜ time docker build -t no-minimize-layers --no-cache -f no-minimize-layers.dockerfile .
0.31s user 0.28s system 2% cpu 28.577 total

It took 28 seconds to build the image, while building the original version with polished layers takes only 19 seconds (Almost 33 % faster).

➜ time docker build -t original --no-cache -f original.dockerfile .
0.25s user 0.28s system 2% cpu 19.909 total

Let’s check the size of the photos.

➜ docker images --filter "reference=*original*" --filter "reference=*no-minimize*"
REPOSITORY           TAG       IMAGE ID       CREATED          SIZE
original             latest    f1363df79c8a   8 seconds ago    356MB
no-minimize-layers   latest    ad3945c8a8ee   43 seconds ago   379MB

The image with modified layers is less than 23MB of those that do not contain layers to the minimum. This is it 6 % decrease. Although it seems a slight difference in this example, the difference will be much larger if you divide each RUN Instructions in multiple lines.

Rule No. 3: Install only what is required

By default, apt-get install It installs the recommended beams as well as the beams that I requested to install. the --no-install-recommends He tells the option apt-get To install only explicitly defined beams, not recommended beams.

➜ time docker build -t without-no-install-recommends --no-cache -f without-no-install-recommends.dockerfile .
0.33s user 0.30s system 2% cpu 29.786 total


➜ docker images --filter "reference=*original*" --filter "reference=*recommends*"
REPOSITORY                      TAG       IMAGE ID       CREATED          SIZE
without-no-install-recommends   latest    41e6e37f1e2b   3 minutes ago    426MB
minimize-layers                 latest    dff22c85d84c   17 minutes ago   356MB

As you can see, the image without --no-install-recommends It is 70 MB larger than the original. This is it 16 % increase in size.

Use Diving The utility to find out the files added to the image – read more about it at the end of the article.

Rule No. 4: Cleaning after installations

origin Dockerfile It includes rm -rf /var/lib/apt/lists/* /var/cache/apt/archives Order apt-get install He orders. This removes the lists of packages and archives that are no longer required after the installation. Let’s see how it affects the size of the image, to achieve this, I will create a new Dockerfile Without the cleaning order.

RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libjemalloc2 libvips libpq-dev

The construction of images takes almost the same time as the original image, which is logical.

➜ time docker build -t without-cleaning --no-cache -f without-cleaning.dockerfile .
0.28s user 0.30s system 2% cpu 21.658 total

Let’s check the size of the photos.

➜ docker images --filter "reference=*original*" --filter "reference=*cleaning*"
REPOSITORY         TAG       IMAGE ID       CREATED          SIZE
without-cleaning   latest    52884fe50773   2 minutes ago    375MB
original           latest    f1363df79c8a   16 minutes ago   356MB

The image without cleaning the largest 19MB of the image that cleans, and this is a 5 % increase.

The worst scenario

What if the four improvements mentioned above are not applied? Let’s create new Dockerfile Without any improvements and image building.

# syntax=docker/dockerfile:1
# check=error=true

ARG RUBY_VERSION=3.4.1
FROM docker.io/library/ruby:$RUBY_VERSION AS base

RUN apt-get update -qq
RUN apt-get install -y curl
RUN apt-get install -y libjemalloc2
RUN apt-get install -y libvips
RUN apt-get install -y libpq-dev

CMD ["echo", "Whalecome!"]
➜ time docker build -t without-optimizations --no-cache -f without-optimizations.dockerfile .
0.46s user 0.45s system 1% cpu 1:02.21 total

Wow, it took more than a minute to build the image.

➜ docker images --filter "reference=*original*" --filter "reference=*without-optimizations*"
REPOSITORY              TAG       IMAGE ID       CREATED         SIZE
without-optimizations   latest    45671929c8e4   2 minutes ago   1.07GB
original                latest    f1363df79c8a   27 hours ago    356MB

The image without improvements is 714 MB of the original image, and this is a 200 % increase in size. This clearly shows how important improvement is DockerfileThe largest pictures take more time to build and consume more disk space.

Always use .DOCKERIGNORE

the .dockerignore The file is similar to .gitignore A file uses GIT. It is used to exclude files and evidence from the context of construction. The context is the collection of files and evidence that is sent to Docker Daemon when creating an image. The context is sent to Docker Daemon as TarBall, so it is important to keep it as small as possible.

If, for any reason, you don’t have .dockerignore A file in your project, you can manually create it. I suggest that you use official bars .dockerignore The file template as a starting point. Below is an example of its shape.

# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.

# Ignore git directory.
/.git/
/.gitignore

# Ignore bundler config.
/.bundle

# Ignore all environment files.
/.env*

# Ignore all default key files.
/config/master.key
/config/credentials/*.key

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/.keep

# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/.keep

# Ignore assets.
/node_modules/
/app/assets/builds/*
!/app/assets/builds/.keep
/public/assets

# Ignore CI service files.
/.github

# Ignore development files
/.devcontainer

# Ignore Docker-related files
/.dockerignore
/Dockerfile*

presence .dockerfile The file is not allowed in the project only except for unnecessary files and evidence (for example, GitHub work tasks from .github Folder or JavaScript dependencies from node_modulesFrom the context. It also helps to avoid adding sensitive information accidentally to the image. For example, and .env The file that contains environmental variables or master.key A file used to decompose accreditation data.

Use diving

All of the above improvements may seem clear when explained. What do you do if you already have a huge picture, and don’t know where to start?

My favorite and most useful tool is diving. Dive is a TUI tool to explore a Docker image, layer contents, and discover ways to reduce the image size. Dive can be installed with your system package manager, or you can use the official Docker image to run. Let’s use the image of our worst scenario.

docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest without-optimizations

Docker layers lose the toolDocker layers lose the tool

In the screen shot above, you can see the most diseases examination. The diving shows the size of each layer, the total size of the image, and the files that were changed (added, modified or deleted) in each layer. For me, this is the most useful feature of diving. By listing files in the right panel, you can easily select the unpopular files and remove the orders you add to the image.

One of the things I really love in diving is that, along with a terminal user interface, it can also provide CI’s friendly product, which can be effective in local development as well. To use it, turn on diving with CI The environment variable has been set to trueThe output is in the screenshot below.

docker run -e CI=true --rm -it -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive:latest without-optimizations

Dive CI friendly directingDive CI friendly directing

My personal preference is to use diving on a scheduled basis, for example, once a week, to ensure that your photos are still in good condition. In the next articles, I will cover the automatic work items to check for Dockkerfile, including diving and hadolint.

Do not distort classes

One of the methods to reduce the size of the image I saw is to try to crush the layers. The idea was to combine several layers in one layer to reduce the size of the image. Docker had a trial option --squashBesides, there were third-party tools such as Docker-Squash.

Although this approach is working in the past, it is currently neglecting and is not recommended to use it. The layers destroyed the basic feature of the Docker’s message in the cache of the layer. Regardless, while using it --squash You can include sensitive or temporary files unintentionally from the previous layers in the final image. This is the approach of everything or nothing lacks accurate control.

Instead of crushing layers, it is recommended to use multi -stage designs. Bars Dockerfile It is already used to build multiple stages, and I will explain how it works in the following article.

Conclusions

Improving Docker, just like any other improvement, It cannot be done once and forgotten. It is a continuous process that requires regular checks and improvements. I tried to cover the basics, but it is important to know and understand. In the following articles, I will cover more technologies and advanced tools that can help make Docker build faster and more efficient.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button