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 0.0.0.0:80 -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', 'user@example.com', '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 0.0.0.0:80 -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: https://hub.docker.com/r/visol/egroupware/~/dockerfile/
# More info: https://github.com/docker-library/php/issues/75
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 \
myphp-has-imap
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
MAINTAINER Chris Fidao
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 0.0.0.0:80 -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.