August 12, 2017

2.5 Ways to Update a Container

We see how to modify a running container and how to build new images that we can use to create new containers.

Naive Way (.5 / 2.5)

In the Naive way to play around and add to a container, we just spin up a new one (Ubuntu in this case) and run bash.

# Start a container
# Sharing a Laravel application from my Mac into the container
docker run -it  \
    -p 80:80 \
    -v ~/Sites/docker/my-app:/opt \
    -w /opt \
    ubuntu:16.04 bash

# Install PHP and run a test server
apt-get update
apt-get install -y php7.0-cli
php -S -t /opt/public

This works. It leaves us with a container who's content and setup isn't reproducable. If this container gets deleted, we lose our work. Additionally, we can't share this container.

🚨 But, this isn't a good way to create or update a container.

What we want is a way to build an image from updates we make to a container. Images are re-usable and sharable!

Exec + Commit (1.5 / 2.5)

Let's create an new image from changes to a container.

We'll do this a slightly different way. Once again, we create another container, but this time we'll run one from the official PHP repository.

Here we'll see that while this works, we are missing PHP's IMAP module, which we want to use in our application.

Let's make some code that calls upon the PHP IMAP module:

# Laravel 5.3 File: routes/web.php
Route::get('/', function ()
    # Connect to fictitious localhost email server
    imap_open('{localhost:143/imap4}INBOX', '', 'password');

    return 'success';

Then we'll get this PHP code running in a new container, using an official PHP image:

docker run -it \
    -p 80:80 \
    -v ~/Sites/docker/my-app:/opt \
    -w /opt \
    php:7.0-cli \
    php -S -t /opt/public

If we view our Laravel application in the browser, we'll see an error since we don't have the PHP-IMAP module installed. The imap_open function doesn't exist!

Let's see how we can install the PHP-IMAP module into this container, and then commit that change into the container to save it as a new image.

# Use "exec" to start a new "bash" process
# inside the already running container
# It's like SSHing into it, but we're not using SSH over a network!
docker exec -it reverent_franklin bash

# Install the dependencies and php-imap module
# Example configuration:
# More info:
apt-get -y install libssl-dev libc-client2007e-dev libkrb5-dev \
    && docker-php-ext-configure imap --with-imap-ssl --with-kerberos \
    && docker-php-ext-install imap

This installs IMAP into the container. If we restart this container, we'll see in the browser that the imap_open function now exists! We instead receive an error saying a stream cannot be opened - it can't connect to our non-existent localhost mail server!

Let's see how to save this container to an image so we can share or re-use it:

docker commit reverent_franklin myphp:with-imap

This creates a new image named myphp and tagged with-imap!

docker run -it \
    -p 80:80 \
    -v ~/Sites/docker/my-app:/opt \
    -w /opt \

Note that we didn't pass it a command - since our container was running the php -S ... command when we committed it, that's what gets used as the command for this images when we run a new container from it. The reason why it does that has to do with out we build images, which we'll see a little bit of next.

Note that if you run docker images, you'll see the image myphp with tag with-imap.

🚨 This is still not the greatest way to do this.

While this leaves us with an image we can push to a repository and share with others, it requires manual work that isn't necessarily something we can automate.

In our next section, we'll see the "best" way to go about this - one that gives us something we can commit to project repositories along side our code, and is more easily shareable.

Dockerfile (2.5 / 2.5)

Let's see the final way to add onto a container, and build a new image. This is the "official" (preferred) way to do such as task, as it involves creating a new file. This file can be comitted into your code, thus helping to fulfill the promise of infrastructure as code.

We'll create a new Dockerfile and have it setup to add in the IMAP module, leaving us with an image we can use to run new containers.

FROM php:7.0-cli


RUN apt-get update \
    && apt-get -y install libssl-dev libc-client2007e-dev libkrb5-dev \
    && docker-php-ext-configure imap --with-imap-ssl --with-kerberos \
    && docker-php-ext-install imap

Once that's saved, we can build a new Docker image using this Dockerfile. We'll name it myphp again, but we'll tag is somethign different - in this case, 1.1-with-imap.

# From ~/Sites/docker, where the Dockerfile is
docker build -t php:imap .

That's it! That creates a new image we can use to run any PHP command we'd like. PHP will have the IMAP module installed!

docker run -it \
    -p 80:80 \
    -v ~/Sites/docker/my-app:/opt \
    -w /opt \
    myphp:1.1-with-imap \
    php -S -t /opt/public

This leaves us with a Dockerfile that we can add alongside our project code and share with others. Instead of passing around larger Docker images, we can share these small text files, knowing that others can build and reproduce the environment needed to run your code.

Looking for a deeper dive into Docker?

Sign up here to get a preview of the Shipping Docker course! Learn how to integrate Docker into your applications and develop a workflow to make using Docker a breeze!

All Topics