Wojtek @suda Siudzinski


Python/Node/Golang/Rust developer, DIY hacker, rookie designer, 3D print junkie. CEO @ Gaia Charge


Single Dockerfile for testing and production using multi-stage builds

In different contexts like development, running tests or serving production, our apps have different needs when it comes to their Docker images. For development, you might want a live-reload server with all necessary dependencies. For testing you probably need some more packages that you definitely don't want on production.

There are ways to go about it, the first one that pops in mind is using multiple Dockerfiles and just telling Docker which one to use. But this approach has its limits, especially along the road, where each file can very easily develop its own life, diverging from each other beyond recognition.

Fortunately since Docker 17.05 we have the concept of multi-stage builds. An ability to declare separate images in one Dockerfile and freely inherit or copy data between each other. This is the exact approach I used when building images for Lox:

#############################################
# Base container with all necessary deps
FROM tiangolo/uvicorn-gunicorn:python3.7-alpine3.8 AS base

ENV HOME=/app 
    BUILD_DEPS="build-base linux-headers postgresql postgresql-dev libffi-dev"
WORKDIR ${HOME}

# Copy the pipenv files
COPY Pipfile ${WORKDIR}/
COPY Pipfile.lock ${WORKDIR}/

# 1. Install system dependencies
# 2. Install pipenv
# 3. Use pipenv to install app deps
# 4. Remove system deps to save space
RUN apk add --no-cache ${BUILD_DEPS} \
    && pip install --no-cache-dir pipenv \
    && pipenv install --system \
    && apk del ${BUILD_DEPS}


#############################################
# Test container from a common base
FROM base AS test
# Same as in the base image but this time we also install the --dev packages
RUN apk add --no-cache ${BUILD_DEPS} \
    && pip install --no-cache-dir pipenv \
    && pipenv install --system --dev \
    && apk del ${BUILD_DEPS}


#############################################
# Live container with Webpack watcher
FROM base AS live
# Install Node.js dependencies
RUN apk add --no-cache nodejs npm
# Install all Webpack dependencies
COPY package.json package-lock.json ./
RUN npm install
# Finally copy the current sources and build the bundle
COPY . .
RUN npm run bundle:build


#############################################
# Final container with the app
FROM base AS production
# Only copy the ready app dir from the live step
COPY --from=live ${WORKDIR} ${WORKDIR}

Note: for the sake of brevity, this Dockerfile doesn't contain all size improvements and will produce slightly bigger images.

Now you're able to build separate images for each of your needs:

Testing

$ docker build -t suda/lox/test --target test .

Development w/ live-reload

$ docker build -t suda/lox/live --target live .

Production

$ docker build -t suda/lox --target production .

P.S.: You might've noticed I used the great Python base image from Sebastián Ramírez and if you want to deploy a Django project using ASGI, I have some instructions that can help you.

comments powered by Disqus