Introduction: Why Data Persistence Matters

Welcome back! In the last lesson, you learned how to build your own Docker image for a simple Flask web app, run it as a container, and access it from your browser. You also saw how easy it is to start, stop, and remove containers. However, there is an important detail about containers that you need to understand as you move forward: containers are ephemeral. This means that any files created or changed inside a container will disappear as soon as the container is removed.

Imagine you are running a web app that writes logs, stores user uploads, or saves configuration files. If you remove the container, all that data is lost. In real-world applications, you almost always need to keep some data between runs. For example, you might want to keep server logs for debugging, or you might need to store user-generated content. This is where data persistence comes in. In this lesson, you will learn how to make sure important files created by your app are saved on your computer, even after the container is gone.

Volumes 101: Options and What We’ll Use

Docker provides a few ways to persist data. The two most common are bind mounts and named volumes. A bind mount lets you map a specific directory or file from your host machine (your computer) into the container. This means anything the container writes to that directory will actually be saved on your host, and you can access it even after the container is removed. A named volume is managed by Docker and is stored in a special location on your system. Both methods are useful, but for this lesson, you will use a bind mount. This approach is simple and makes it easy to see the files your app creates right in your project folder.

You will mount a logs directory from your host into the container. This way, when your Flask app writes log files, they will be saved on your computer and will not disappear when the container is removed.

App Update: Write Logs to a File

To see data persistence in action, you will update your Flask app so that it writes a log entry every time someone visits the home page. The app will create a logs directory (if it does not already exist) and append a line to logs/access.log with the current date and time.

Here is the updated app.py:

from flask import Flask
import datetime
import os

app = Flask(__name__)

# Ensure the logs directory exists inside the container
os.makedirs("logs", exist_ok=True)

@app.route("/")
def home():
    with open("logs/access.log", "a") as f:
        f.write(f"Accessed at {datetime.datetime.now()}\n")
    return "Hello with logging!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=3000)

This code checks if the logs directory exists and creates it if needed. Every time the root URL is accessed, it appends a line to logs/access.log with the current timestamp. The log file path is relative to the app’s working directory, which keeps things simple and predictable.

Rebuild the Image with Logging

Since you have changed your application code, you need to rebuild your Docker image. This is a good reminder that any time you change your app’s code or dependencies, you must rebuild the image for those changes to take effect in new containers.

Your Dockerfile for this app looks like this:

FROM python:3.12-slim

WORKDIR /app

COPY app.py .

RUN pip install flask

EXPOSE 3000

CMD ["python", "app.py"]

Notice that the Dockerfile sets the working directory to /app, copies in your app.py, installs Flask, exposes port 3000, and sets the default command to run your app. To build the new image, use the following command in your terminal, making sure you are in the docker-app directory:

docker build -t my-web-app:logging .

The -t my-web-app:logging option gives your image a new tag, so you can easily tell it apart from earlier versions.

Run the Container with a Bind Mount

Now you are ready to run your container and mount a directory from your host into the container. First, create a logs directory on your host machine. This is where the log files will be saved:

mkdir -p logs

Next, run your container with the -v option to mount the host’s logs directory into /app/logs inside the container. On macOS or Linux, you can use $(pwd)/logs to refer to the full path of the logs directory. On Windows, you would use %cd%\logs. Here is the command for macOS/Linux:

docker run -d --name my-logging-app -p 3000:3000 \
  -v $(pwd)/logs:/app/logs \
  my-web-app:logging

This command starts the container in detached mode, names it my-logging-app, maps port 3000, and mounts your host’s logs directory into the container. Now, when the app writes to /app/logs/access.log, the file will actually be created and updated in your host’s logs directory.

Verify Persistence Across Container Removal

To test that everything is working, open your browser and visit http://localhost:3000. Each time you refresh the page, the app will write a new line to the log file. You can check the contents of the log file on your host with:

cat logs/access.log

You should see output like this:

Accessed at 2024-06-10 15:23:01.123456
Accessed at 2024-06-10 15:23:05.789012

Now, stop and remove the container:

docker stop my-logging-app
docker rm my-logging-app

Check the log file again:

cat logs/access.log

You will see that the log file still exists and contains all the entries you made. This proves that the data persisted on your host, even after the container was removed.

Common Pitfalls and Quick Fixes

When working with bind mounts, there are a few common issues you might run into. If you use the wrong path for the host directory, Docker might create a new directory in an unexpected place, or the mount might fail. Always double-check your paths and use absolute paths if you are unsure. If the logs folder does not exist on the host, Docker will usually create it, but it is a good habit to create it yourself first.

Permissions can also cause problems. If your user does not have write access to the host directory, the container might not be able to write logs. On some systems, like macOS with Docker Desktop, you may need to give Docker permission to access certain folders. On Linux, SELinux can sometimes block access to mounted directories. If you see permission errors, check your Docker settings and folder permissions.

Another thing to remember is that if you change your app code, you need to rebuild the image before running a new container. If you forget, you might be running an old version of your app.

Summary & Next Steps

In this lesson, you learned why data persistence is important when working with Docker containers and how to use bind mounts to save data from your containers onto your host machine. You updated your Flask app to write logs, rebuilt your Docker image, and ran a container with a bind mount to ensure log files persisted even after the container was removed. You also learned about common pitfalls with bind mounts and how to avoid them.

Next, you’ll get hands-on practice using bind mounts to persist data, reinforcing these concepts and preparing you for real-world Docker projects.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal