How to Update Docker Containers on File Change


I used to rebuild my images and restart my containers on every file change. It was the closest I could get to a live reload at the time.

Poor Solution: Rebuilding containers

Specifically, I would run this bash script every time I made a change to a file: build.sh.

# !/bin/bash

# Set default names for image and container
imageName=myImage
containerName=myContainer

# Build image with the same image name
echo Building new image...
docker build -t $imageName -f Dockerfile  .

# Remove existing container running old image
echo Deleting old container...
docker rm -f $containerName

# Delete old images that:
# 1. No longer have a reference
# 2. Do not currently have a container using it
echo Deleting old image...
docker image prune

# Run new container
echo Running new container...
docker run -d -p 5000:5000 --name $containerName $imageName

This script ran a docker build and docker run while cleaning up old images and containers.

It was fairly suboptimal in day-to-day development with such slow iterations.

Better Solution: Using Bind Mounts

In order to prevent creating a new image on each file change, we can use bind mounts.

Suppose my project is located at /server on the host machine (the computer I’m developing on) and I mount this directory into the container. This /server directory will be referenced by its path on the host machine. This means our local directory will overwrite the respective portion of the image when the container is started.

We will only need to build the image once for tiny code changes and until the installed dependencies need to be changed.

We can use docker-compose for this. We specify our container configuration using docker-compose.yml.

Let’s say this is my project structure for a Flask application I’ve been developing.

📂project
 ┣ 📜 Dockerfile
 ┣ 📜 docker-compose.yml
 ┗ 📂 server
    ┣ 📜 main.py
    ┣ 📜 requirements.txt
    ┗ 📜 every other file in my project...

For the sake of being thorough, this is what my Dockerfile looked like.

FROM ubuntu:16.04
RUN apt-get update -y && apt-get install -y python-pip python-dev build-essential
RUN pip install --upgrade pip
COPY server/requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
COPY server /app
ENTRYPOINT ["python"]
CMD ["app/main.py"]

In our docker-compose.yml, we specify where the Dockerfile resides in build: context. Mine is in ., or the current directory (same directory as docker-compose.yml).

The volumes key is where we specify the bind mount. We’re mounting our /server folder to our container, and we’ll be accessing it from our working directory, /app.

version: '3'
services:
  web:
    build:
        context: .
    ports:
      - 5000:5000
    volumes:
      - ./server:/app

Now we can change our bash script, build.sh, to run the following commands:

# Start up the container
docker-compose up -d
# Check that container is up and running
docker-compose ps

Changes to the mounted code files will be picked up by the container and reflected by our dockerized process.

No need to restart anything!