Slimming a Rails Application with SlimToolkit

Theofanis Despoudis
← Slim Blog

One of the best ways to improve the build times of container images is to analyze the final image and trim off unwanted and unnecessary files. Traditionally, this process is performed with a .dockerignore file by combining RUN commands and squashing layers.

Sometimes, however, you don’t want to change anything in your Docker container image (or Dockerfile instructions), but you do want to minify it more intelligently. In that case, you want to use SlimToolkit (previously DockerSlim).

In this tutorial, we will explore some practical ways to slim a container using SlimToolkit. SlimToolkit is like a Swiss Army knife for dissecting container internals, and the insights that it provides enable you to analyze, optimize, and deploy your product quickly. We will start by showing you how to containerize a simple Rails application. Then, we’ll include extra files and directories while handling the rest of the final build. Finally, we will introduce you to some additional options for slimming containers.

Let’s get started.

Slimming a Simple Rails Application Container

SlimToolkit is very versatile. It can be used in containers running Node.js, Python, Ruby, Java, Golang, Rust, Elixir, or PHP on Ubuntu, Debian, CentOS, Alpine, or even Distroless. In this tutorial, we will show you how to slim a simple Rails application.

First, you need to create a new Rails app. We’ll use the following command:

$ env RBENV_VERSION=2.7.4 rbenv exec rails new /Users/theo.despoudis/Workspace/hello-world --webpack=react      --skip --database=postgresql

We also specified a Ruby and Rails version to use and created a new React + Rails app with Postgres database provider.

Next, you need to delete and then create an empty Gemfile.lock: (This will fix issues when you run the bundler install command inside the container.)

    $ cd hello-world
    $ rm Gemfile.lock
    $ touch Gemfile.lock

Then, create a Dockerfile that uses the official Ruby image to install Rails dependencies:


    # syntax=docker/dockerfile:1
    FROM ruby:2.7.4
    RUN curl -sS | apt-key add -
    RUN echo "deb stable main" | tee
    RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn
    WORKDIR /myapp
    COPY . /myapp/
    COPY Gemfile /myapp/Gemfile
    COPY Gemfile.lock /myapp/Gemfile.lock
    RUN bundle install

    EXPOSE 3000
    ENTRYPOINT ["./scripts/"]

We used the following starting script:


    set -e

    bundle exec puma -C config/puma.rb

Now, build the image:

    $ docker build -t app .

Use the official Postgres image to provision the database:

    $ docker run --name postgresql-container -p 5432:5432 -e
    POSTGRES_PASSWORD=somePassword -d postgres

    $ psql --u postgres -h
    Password for user postgres:
    psql (13.4, server 14.1 (Debian 14.1-1.pgdg110+1))
    WARNING: psql major version 13, server major version 14.
        Some psql features might not work.
    Type "help" for help.
    postgres=# create database hello_world_development
    postgres=# exit;

Then, run the application to verify that it works:

   $ docker run -e
    "DATABASE_URL=postgres://postgres:somePassword@" -p 3000:3000 app

Using docker inspect, you can see that the image takes up 1.1GB of storage:

$ docker inspect app | jq '.[] | .Size' | numfmt --to iec --format "%8.4f"

Now you can use SlimToolkit to reduce the image size. For this tutorial, we will use the official Docker image to run the experiments. First, you need to pull the latest image:

    $ docker pull slim/slim

Then, run the build command to slim the container image down in just one step:

    $ docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock slim/slim build app --http-probe=false

We disabled http-probing using the --http-probe=false flag because we just wanted to minify the image without probing the live container. Later on, we will enable http-probe with the --continue-after flag to continue executing the build command. This will help us control the completion of the build.

Now you can use the newly created image to inspect your cost savings:

    $ docker inspect app.slim | jq '.[] | .Size' | numfmt --to iec --format "%8.4f"

Wow! Now it's only 80MB total!

You can also run the application in the container to verify that it works:

 $ docker run -e "DATABASE_URL=postgres://postgres:somePassword@" -p 3000:3000 app.slim
      ! Unable to load application: LoadError: cannot load such file -- zlib

Oops! It looks like we need to enable the http-probe in this case, since the Rails application lazy-loads native modules like zlib and the Postgres shared libraries at runtime. Not all applications behave this way, but some (such as Rails and Django) do. The SlimToolkit does not know this unless it probes the application to verify that it is up and running and that all of its runtime modules were loaded properly. If a module was not loaded, SlimToolkit can remove it from the final build.

Now, you need to run the full-fledged build command to make it work with Rails:

    $ docker run -e "DATABASE_URL=postgres://postgres:somePassword@" -it --rm -v /var/run/docker.sock:/var/run/docker.sock slim/slim build app
    cmd=build state=http.probe.starting message=WAIT FOR HTTP PROBE TO
    cmd=build info=http.probe.summary total='2' failures='1'

In the above command, SlimToolkit created a temp container and probed the containerized application so that it knows which essential modules to keep in the final image. You can inspect the size again at this point (notice that it’s just a few MB larger):

    $ docker inspect app.slim | jq '.[] | .Size' | numfmt --to iec --format "%8.4f"

Now, run the slimmed image:

    $ docker run -e "DATABASE_URL=postgres://postgres:somePassword@" -p 3000:3000 app.slim

Figure 1: Rails App Running in a Minified Image

It works!

Now that you know the basics of SlimToolkit and HTTP probes, we’ll explain how to control the execution of the build command using the --continue-after flag.

Controlling When to Finish Executing the Build Command

HTTP probes give SlimToolkit the ability to detect lazy loaded modules that are required for the application at runtime. There are various flags that can control its behavior, including:

  • --http-probe-retry-count: This controls the number of retries for each HTTP probe. If you want to make sure that the probe will hit a valid endpoint after a certain period of time, give this a higher number and control the wait period with the --http-probe-retry-wait flag.
  • --http-probe-ports: This allows you to specify a list of ports to probe. By default, SlimToolkit will use the EXPOSE ports as defined in the DockerFile image.
  • --http-probe-apispec: This runs a probe for an API specification like Swagger, which improves discoverability of endpoints and resources.

Although these flags are useful, it takes a lot of time to figure them out, which is particularly inefficient when you are still developing the application or when you want to test specific scenarios. If you want to have better control over when the build process completes, you can just manually send a keystroke or a signal to finish building the image with the --continue-after flag.

Let’s run the previous build command with the --continue-after flag:

    $ docker run -e
            "DATABASE_URL=postgres://postgres:somePassword@" -it --rm -v
            /var/run/docker.sock:/var/run/docker.sock slim/slim build
            --copy-meta-artifacts . --continue-after=enter app

    cmd=build info=continue.after message='provide the expected input to
    allow the container inspector to continue its execution' mode='enter'

(You will be able to review the info message describing the procedure.) It will start probing the endpoints as specified, but now it will wait for a trigger to complete. We used the enter flag in this case, which means that we have to press the enter key to continue. If you’ve been following along, press it now to see the build process finish:

    cmd=build state=container.inspection.finishing

The --continue-after flag allows for different combinations of flags out of enter | signal | probe | exec | timeout-number-in-seconds | container.probe.

You can emit an OS signal from the command line, for example, or wait for an executable to finish or a specified timeout (in seconds) to be reached. In this way, you will gain finer control over the build process and the accuracy of the minification. In practice, you will find that you must use flags very efficiently to deliver the most optimized image using SlimToolkit. Next, we will show you how to include extra files in the finished image.

Including Extra Files and Directories in Your Minified Image

Sometimes your application container needs to contain specific files, directories, executables, or binary images in the final form. If SlimToolkit trims them from the final build as part of the minification process for some reason, you’ll want to put them back. For example, it might remove erb templates or migrations folders if it detects that are unused.

For that, SlimToolkit offers an extensive list of flags to control which resources should be included. For example, it offers the following –include-* flags:

  • --include-path: This includes all contents of the specified folder path into the final image. For instance, to include the migrations folder in our example, you would run:
  $ docker run -e
          "DATABASE_URL=postgres://postgres:somePassword@" -it
          \--rm -v /var/run/docker.sock:/var/run/docker.sock
          slim/slim build --include-path /myapp/db/migrate app
  • --include-path-file: This includes a specific file into the final image.
  • --include-bin: This includes a specific binary into the final image. SlimToolkit will check its dependencies and include them into the final build.
  • --include-exe: This includes a specific executable into the final image. SlimToolkit will check its dependencies and include them into the final build.
  • --include-cert-all: This will try to keep any certificates installed in the image. For example, it will use detectors to copy certificates from known locations. If you’re using an Ubuntu image, it will copy them from the /etc/ssl/certs folder.

External modules or shared libraries that the application needs (such as elastic search modules or a Postgres shared library that must be loaded) are examples of included files. SlimToolkit might not always deem them necessary when you include them in the Dockerfile, so it might filter them out from the final image.

Other Useful Flags

You can also try experimenting with the following flags:

  • --http-probe-crawl: This will transform your HTTP probe into a web crawler that will follow all the links it finds in the target endpoint.
  • --pull: This pulls an image from a registry instead of the local image list.
  • --new-entrypoint, --new-cmd, --new-expose, --new-workdir, --new-env, --new-label, and --new-volume: Each one of these flags lets you customize the DockerFile’s respective instructions (entrypoint, cmd, expose, workdir, env, label, and volume).

Moreover, you can also specify the container runtime (via the --cro-runtime flag) or any step of the Docker build process. This fine-grained control is extremely valuable, making SlimToolkit a must-use tool for modern teams using containers in production.

Next Steps with SlimToolkit

SlimToolkit offers many other flags and tools for inspecting, optimizing, and refining container images. You can start by reviewing some of the Slim.AI tutorials, as they are the main providers of SlimToolkit and have deep knowledge of container development and optimization. You should also review SlimToolkit’s official README, since it is essentially a reference guide for the tool. Finally, you can join the Slim SaaS Early Access program to analyze thousands of public container images or scan your own using the online panel.

Make security collaboration easier today

Join the waitlist to try out Slim's shared workspace for communicating and coordinating vulnerability fixes with your software vendors.
Responsive HubSpot Form

Join our Beta

Take the complexity and frustration out of coordinating vulnerability fixes with your vendors.

  • Communicate directly in the platform to assign owners, due dates and negotiate fixes
  • Get a view into the status of each vulnerability
  • Receive notifications the moment vulnerabilities are fixed

Additionally, our Beta users get access to:

  • Multiple vulnerability scanners
  • SBOM generation
  • Reachability analysis
  • Enhanced container intelligence software
  • Dedicated Support

Join our Beta

Take the frustration out of vulnerability fixes with software vendors directly on our platform.

  • Assign owners, set due dates, track vulnerability statuses, and get instant fix notifications.
  • Beta users gain access to multiple scanners, SBOM generation, reachability analysis, enhanced container intelligence, and dedicated support.