Skip to content

6. Docker Workflows

Michael Härtl edited this page Sep 9, 2015 · 2 revisions

Docker is very versatile so you can come up with several different workflows.

Note: Consider the below to be "Work in progress". It very much represents our understanding of Docker workflows. But as Docker tools constantly evolve, things may change pretty fast. And what was considered to be good practice today may be outdated tomorrow. You're advised to read the below with care and come up with your own ideas.

Dockerfile Based

  • No docker registry required
  • Slower deployment due to extra build step

This is a very simple workflow: In each environment the runtime image is built from the Dockerfile. Developers need to be informed whenever the Dockerfile changes so that they rebuild their local image.

No images are shared between developers, so the initial and also each consecutive build step could take quite some time, depending on how much the Dockerfile has changed since the last build in this environment.

Docker Image Based

  • Requires a docker registry (either self-hosted or from 3rd party)
  • Quick and simple deployment

Here we can take two approaches: With or without a base image. In both cases the Dockerfile should be organized in a way, that the rather static or less changing parts should be on top of the Dockerfile and the dynamic or frequently changing parts (e.g. COPY . /var/www/html) at the bottom of the file.

Without A Base Image

We use a single Dockerfile and each time we want to make a deployment, we create a new tagged image and push it to the registry. This image can then be pulled to production.

One drawback here is, that each deployment image contains a full copy of your source code, even if you only changed a couple of lines since the last deployed image.

It's also important that the developers alway pull the last deployed image to their machine, as otherwhise docker couldn't reuse cached layers the next time it builds a new image.

Using A Base Image

Here we use two Dockerfiles:

  • One for the base image, e.g. Dockerfile.base and
  • one for the final image(s), which extends from the base image

The base image stays the same for some time and is used as basis for many deployment images. This has the advantage that (hopefully) many files from the base image can be reused, whenever a new deployment image is built. So the actual release image layer will have a very small footprint and, in effect, once a base image is shared among developers and on production, there's not much data to be moved around with each release.

To start we'd first move the current Dockerfile and create a base image:

mv Dockerfile Dockerfile.base
docker build -f Dockerfile.base -t myregistry.com:5000/myapp:base-1.0.0
docker push myregistry.com:5000/myapp:base-1.0.0

Now we have a base image as version base-1.0.0. For ongoing development we take it from there and use a minimal second Dockerfile that is based on this image:

FROM myregistry.com:5000/myapp:base-1.0.0
COPY composer.json /var/www/html/
COPY composer.lock /var/www/html/
RUN composer self-update && \
    composer install --no-progress
COPY . /var/www/html
RUN mkdir runtime web/assets \
    && chown www-data:www-data runtime web/assets

So other developers will now use this simplified Dockerfile for their daily work. They don't have to rebuild all the layers from the base image and will only stack their local changes on top.

We'll also use this file for the final deployment images:

docker build -t myregistry.com:5000/myapp:1.0.0
docker build -t myregistry.com:5000/myapp:1.0.1
docker build -t myregistry.com:5000/myapp:1.0.2
...

Note: Version numbers here are only used to make the example clearer. You'd probably use a different versioning scheme, especially if you do continous deployments.

After some releases your latest code will differ more and more from the base image. So more and more files will have changed since it was created and this will make your deployment images bigger and bigger with each release. To avoid this you can create an updated base image from your latest code:

docker build -f Dockerfile.base -t myregistry.com:5000/myapp:base-1.1.0
docker push myregistry.com:5000/myapp:base-1.1.0

Note: You may want to modify Dockerfile.base to extend from your old base image first. This will save extra space, but add more file layers to your docker images over time, which is limited to 127.

Now we have an updated base image and can use that for ongoing development. We therefore have to update the FROM line in the Dockerfile:

FROM myregistry.com:5000/myapp:base-1.1.0