Meet DockerSlim's Compose Mode

Optimize a multi-tier app with a single command
Ian Juma
Jul 08, 2022


Photo by Jeremy Bezanger on Unsplash

DockerSlim is built to work with most common application frameworks — from simple web servers to complex multi-tier services — to minify your containers, making them better, smaller, and more secure.

You can find plenty of posts on our blog on how to use DockerSlim with different frameworks and apps from Rails to Cloud Native Buildpacks, and even how to automate testing and CI/CD in different tooling.

Being developer-first and wanting to always make sure the DockerSlim experience is built into native developer workflows, we’re excited to launch Docker Compose support inside of DockerSlim. We wanted to enable this functionality natively when using DockerSlim to gain both the benefits of container slimming and minification, and combine it with easy orchestration without having to use multiple tools and CLIs.

With DockerSlim, you can optimize containers for better security, size, and velocity. To do so, DockerSlim needs to launch the target container and analyze its behavior at runtime. Since many real-world containers depend on other services and databases, it's rarely possible to run a container in isolation. Luckily, Docker Compose solved this problem a long time ago.

Docker Compose is a really great way to orchestrate and model containers running multi-tier complex applications with dependencies through a common DSL–in this case YAML. In YAML (beloved to all developers), you can define the tiers of the application, the dependencies between them, and even other services you’d like to apply to the container like monitoring or service listeners, and environment variables. You can define your target service and its surroundings in one docker-compose.yml file.

Automating deployment of a multi-tier application and the dependencies is tedious and cumbersome. How does one cleanly configure the services, networking, and other related dependencies; it is simply error prone and easy to misconfiguration. Luckily Docker Compose makes this much easier. You simply defined the services, networks, service flags and even adhoc commands when building your containers. Wrap that in a docker-compose.yml file, and docker orchestrates everything else for you.

Below you’ll find a real-world example of how you can orchestrate a set of services, including postgres and a monitor service, that catches changes to postgres and updates Redis database keys, using Docker Compose flags from inside DockerSlim.

Find everything you need to try this example yourself in this demo app repo: https://github.com/ianjuma/change-monitor

Minifying & Orchestrating a Multi-Tier Application in DockerSlim

First let’s take a look at the Docker Compose file we’re going to be calling from inside DockerSlim.

version: '3.7'
services:
  monitor:
    image: change-monitor_monitor:latest
    restart: unless-stopped
      #healthcheck:
      #test: [ "CMD", "pg_isready", "-q", "-d", "postgres", "-U", "root" ]
      #timeout: 45s
      #interval: 10s
      #retries: 10
    environment:
      - PDB_HOST=postgres
      - PDB_NAME=product
      - PDB_USER=postgres
      - PDB_PASS=postgres
      - RDB_HOST_PORT=cache:6379
      - LOG_LEVEL=debug
      - ENABLE_TRIGGERS=true
    build:
      context: .
      dockerfile: Dockerfile
    depends_on:
      - postgres
      - cache
 postgres:
    image: postgres:10.5
    restart: always
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_DB=postgres
      - POSTGRES_PASSWORD=postgres
      - DB_NAME=product
    logging:
      options:
        max-size: 10m
        max-file: "3"
    ports:
    - '5432:5432'
    volumes:
      #- postgres-data:/var/lib/postgresql/data
    - ./sql/products.sql:/docker-entrypoint-initdb.d/products.sql
      #- ./sql/01-init.sh:/docker-entrypoint-initdb.d/01-init.sh
  cache:
    image: redis:6.2-alpine
    restart: always
    ports:
      - '6379:6379'
    command: redis-server --save 20 1
    #volumes:
    #- cache:/data
volumes:
  cache:
  postgres-data:

In it you’ll find the services that need to be started and orchestrated with the container initialization, including monitoring services, a build context, environment variables, databases and their dependencies.

What you can see is that this service contains a cache that gets updated by a service that listens to database events. This is an example to simply demonstrate how Docker Compose can be leveraged from within DockerSlim CLI, without changing any of your code or configuration.

Show Me the Code:

Now that we are familiar with the services contained in our docker-compose.yml file, let’s say we’d like to minify & optimize a target container {target}, and also optimize a service within our Docker Compose file, we would start with a command that looks like this:

docker-slim build --show-clogs --compose-file docker-compose.yml --http-probe=false --target-compose-svc monitor --tag monitor-minified:v1.0.0

By including the --compose-file docker-compose.yml flag in our DockerSlim CLI command we can then include our docker-compose.yml file to be started with our minification process. Another important flag to note is the `--target-compose-svc`, this is the service we intend to optimize within the compose file. The rest of the flags are the regular flags you would run as part of your DockerSlim minification process.

The --tag flag enables you to specify a specific tag for the optimized output image, and the rest are more or less the global tags you’d use when running DockerSlim such as log-level flags and other helpful output (read more here).

Let’s open the file with vscode and run the following command code docker-compose.yml starts our compose file

Next let’s initialize the service we want to optimize sql/01-init.sh

This will run the following script:

#!/bin/sh
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname
"$POSTGRES_DB" <<-EOSQL
  CREATE DATABASE $DB_NAME;
EOSQL

This first creates the database, and then populates the table with a few entries.

The example we will now demonstrate is how to update an entry in the database through DockerSlim using Docker Compose in the same CLI.

Updating Our Service

Next we’d like to access the products table inside the database, so we run the command sql/products.sql

-- Creation of product table
CREATE DATABASE product;
\c product

CREATE TABLE IF NOT EXISTS product (
  product_id INT NOT NULL,
  name varchar(250) NOT NULL,
  active boolean NOT NULL,
  quantity int NOT NULL,
  price INT NOT NULL,
  PRIMARY KEY (product_id)
);

INSERT INTO product (product_id, name, active, quantity, price)
VALUES (1, 'omo', True, 10, 1);

INSERT INTO product (product_id, name, active, quantity, price)
VALUES (2, 'pencil', True, 10, 2);

INSERT INTO product (product_id, name, active, quantity, price)
VALUES (3, 'blue band', True, 10, 5);

INSERT INTO product (product_id, name, active, quantity, price)
VALUES (4, 'washing powder', True, 10, 2);

-- update product set active=False where product_id=1; |

In order to demonstrate how the cache service works, we will update one of the items in the table.

So if we remember our DockerSlim script above:

docker-slim build --show-clogs --compose-file docker-compose.yml --http-probe=false --target-compose-svc monitor --tag monitor-minified:v1.0.0

It includes a debug flag, which puts the docker-slim application in a debug mode, where it spews out more informational logs that in most cases should be irrelevant. It enabled me to debug the app better, but is not necessary for the majority of cases. You can also see that we are building an optimized image, and we want our target service for the minification is the monitoring service, which will be output to a specific tag: monitor-minified:v1.0.0

Once this is run we will see a few things in the log output.

First, you will notice that HTTP probing has been disabled, as the service isn’t needed (and this can of course be toggled on or off with the relevant flag). Once the command is run, you will see the container dependencies initialized.

We will then manually exercise the container, to manually update the items in the Postgres table.

If we run a quick check with docker ps, we’ll be able to see all of our dependencies along with the monitor service we would like to modify through minification.

As we do this, DockerSlim will be waiting for us to exercise the application.

To do so you’ll then need to run the exec command for the container docker exec -ti {container ID}

In this case we will exercise the Postgres container, upon which the change-monitor service is dependent that will listen for any changes to the ‘active’ field of the table within it.

We then move ahead to making the relevant changes to the products table, by running psql -U postgres and then \c product to connect to the relevant table, and then select * from product;

This will bring up the product table we want to modify, which was created through the init script we ran earlier.

We will now update one of these entries, and effectively update the application that one of the items is no longer available and cannot be sold by changing its state from active = true to active = false.

update product set active='false' where product_id=1;

In order to check that the update was successful we should now see an update in our Redis service. We can check this by running the same exercise command now into the Redis container docker exec -ti {container ID}, after entering the redis-cli, we will be able to see one update.

We can also request to see the change by running the get command.

Checking the Logs & Adding a Layer of Security

As we can see the service has been updated.

Once you have made the relevant change, DockerSlim will wait on the user’s prompt to continue. When you click enter, you should see the monitor log that will show the event received.

If we continue we can see that Docker Slim exercised the application, and minified the application in the process, slimming it down to 4.7MB.

Another thing you get out of the box with DockerSlim, is an added layer of security through the SecComp and AppArmor profiles, which improves the container security posture in the process of minification. By only allowing specific system calls that occurred when the app was being minified., DockerSlim adds an additional layer of security in the process.

We hope you found this tutorial useful for leveraging Docker Compose with DockerSlim to orchestrate and minify complex multi-tier containerized applications.

You can find everything you need to try this example yourself in this demo app repo: https://github.com/ianjuma/change-monitor

Also feel free to give DockerSlim a ⭐ if you think we’re doing good work.

https://github.com/docker-slim/docker-slim

Related Articles

5 Ways Slim Containers Save You Money

Do slim containers really save you money on your cloud bill? Are there cost advantages to smaller containers? Find out here.

Chris Tozzi

Container Insights: Dissecting the World's Most Popular Containers

Join Ayse Kaya in this series, as she creates her 2022 Container Report Chalk Full of Important Security Findings for Developers.

Ayse Kaya

Analytics & Strategy

What We Discovered Analyzing the Top 100 Public Container Images

Complexity abounds in modern development

Ayse Kaya

Analytics & Strategy

2022 Public Container Report

Vulnerabilities continue to increase and developers are struggling to keep up.

Ayse Kaya

Analytics & Strategy

Cloud Development Is Still Too Manual & Complex

Lessons we learned from interviewing more than 30 developers

John Amaral

CEO

Five Things You Should Never Ship to Production in a Container

Here is our take on five things to avoid when creating a container or shipping it to production.

Chris Tozzi

Getting Started with Multi-Container Apps

Up your container game with Docker Compose

Nicholas Bohorquez

Contributor

Serverless Applications and Docker

How to Scale the Latest Trend in Infrastructure

Pieter van Noordennen

Growth

The Squeak Interview

CEO John Amaral joins Chris on his livestream

Where Do You Store Your Container Images?

Container Registry Options are Growing in Number and Complexity

Pieter van Noordennen

Growth

Why Developers Shouldn't Have to Be Infrastructure Experts, Too

Simplifying processes required to containerize and deploy cloud-native apps.

Chris Tozzi

A New Workflow for Cloud Development

Leverage the benefits of containerization without the headaches & hassle

John Amaral

CEO

Why Don’t We Practice Container Best Practices?

Container best practices are easy to understand, hard to do

John Amaral

CEO

Automatically reduce Docker container size using DockerSlim

REST Web Service example using Python/Flask

John Amaral

CEO

Building Apps Using Cloud Native Buildpacks

Getting started with this innovative technique

Vince Power

Contributor

Comparing Container Versions with DockerSlim and Slim.AI

See differences between your original and slimmed images

Pieter van Noordennen

Growth

Five Proven Ways to Debug a Container

When Things Just Are Not Working

Theofanis Despoudis

Contributor

Reducing Docker Image Size - Slimming vs Compressing

Know the difference

Pieter van Noordennen

Growth

Quick Start Guide

Slim Developer Platform Early Access

What’s in your container?

Why Docker Layers matter for container optimization

Pieter van Noordennen

Growth

Creating a Container Pipeline with GitLab CI

Shipping containers the easy way

Nicolas Bohorquez

Contributor