Baeldung Pro – Ops – NPI EA (cat = Baeldung on Ops)
announcement - icon

Learn through the super-clean Baeldung Pro experience:

>> Membership and Baeldung Pro.

No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.

Partner – Orkes – NPI EA (cat=Kubernetes)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Overview

When developing with Docker containers, it’s best practice to store important data in Docker volumes. Docker volumes ensure the data persists since they exist independently of a container’s lifecycle. To enhance the development experience, we can back up a Docker container along with its data volumes. However, including the data volumes in the backup isn’t straightforward.

In this tutorial, we’ll use a practical example to show how we can back up and restore a Docker container along with its data volumes.

2. Setting up the Container and Data Volume

Before we proceed, we need to ensure the container and volume exist.

2.1. Creating a Container With a Data Volume

To illustrate, let’s create and start a MySQL container:

$ docker run --name mysql_container -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=wordpress -v mysql_data:/var/lib/mysql -d mysql:5.7

Here’s what the command does:

  • –name mysql_container – names the container mysql_container
  • -e MYSQL_ROOT_PASSWORD=root – sets the MySQL root password to root
  • -e MYSQL_DATABASE=wordpress – sets wordpress as the database name
  • -v mysql_data:/var/lib/mysql – attaches the volume mysql_data as where the database stores its data
  • -d mysql:5.7 – instructs the container to run in the background using the mysql:5.7 image

Above, the MySQL container stores its data in the named volume mysql_data.

2.2. Verifying the Volume Our Container Uses

Next, let’s inspect the volume associated with the specific container mysql_container:

$ docker inspect mysql_container | grep Mounts -A 10
        "Mounts": [
            {
                "Type": "volume",
                "Name": "mysql_data",
                "Source": "/var/lib/docker/volumes/mysql_data/_data",
                "Destination": "/var/lib/mysql",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }

The container mysql_container contains the volume mysql_data for the database.

3. Backing Up the Container and Its Data Volume

In this section, let’s back up the container and its associated volume.

3.1. Backing Up the Image

To begin, let’s use the docker save command to back up the image:

$ docker save -o mysql_image_backup.tar mysql:5.7

Here’s the breakdown:

  • -o mysql_image_backup.tar – saves the image to mysql_image_backup.tar
  • mysql:5.7 – name of MySQL image

The command above creates a TAR archive of the image:

$ ls
mysql_image_backup.tar

Our current working directory now contains mysql_image_backup.tar, the TAR archive of the image, enabling us to restore it later.

3.2. Backing Up the Volume

In this step, let’s back up all MySQL data files using the tar command. To clarify, we back up the entire MySQL volume:

$ docker run --rm -v mysql_data:/data -v $(pwd):/backup busybox tar cvf /backup/mysql_data_backup.tar /data

This command mounts the MySQL data volume inside a temporary container, compresses its content, and saves it as the TAR archive mysql_data_backup.tar. To clarify, the TAR archive is stored in the current directory.

3.3. Backing Up Metadata

Here, we combine inspecting the container mysql_container and output redirection:

$ docker inspect mysql_container > mysql_metadata.json

This command backs up the container’s metadata in the JSON file mysql_metadata.json. Thus, we can restore the container with its original settings.

3.4. Automating the Backup Process

To make the backup process easier, we can automate it with the help of a simple Bash script:

#!/bin/bash

# Set variables
CONTAINER_NAME="mysql_container"
IMAGE_NAME="mysql:5.7"
VOLUME_NAME="mysql_data"

# Backup filenames
IMAGE_BACKUP="mysql_image_backup.tar"
VOLUME_BACKUP="mysql_data_backup.tar"
METADATA_BACKUP="mysql_metadata.json"

echo "Starting backup process..."

# Backup the Docker image
echo "Saving Docker image..."
docker save -o "$IMAGE_BACKUP" "$IMAGE_NAME"

# Backup the data volume
echo "Saving volume data..."
docker run --rm -v "$VOLUME_NAME":/data -v "$(pwd)":/backup busybox tar cvf "/backup/$VOLUME_BACKUP" /data

# Backup container metadata
echo "Saving container metadata..."
docker inspect "$CONTAINER_NAME" > "$METADATA_BACKUP"

echo "Backup completed successfully!"

Let’s make the script executable:

$ chmod +x backup_script.sh

Here’s a sample of the output the script displays:

$ ./backup_script.sh
Starting backup process...
Saving Docker image...
Saving volume data...
...
Backup completed successfully!

Now, we’ve automated backing up the container image as well as its volume data.

4. Restoring the Container and Its Data Volume

Before restoring, let’s remove the existing container, volume, and image to ensure we restore from the backup instead of using the already existing data:

$ docker stop mysql_container && docker rm mysql_container

This command stops and removes the container mysql_container. Next, let’s remove the volume:

$ docker volume rm mysql_data
mysql_data

Further, let’s remove the image:

$ docker rmi mysql:5.7

At this point, we can proceed with the restoration steps.

4.1. Loading the Container Image

Here, let’s load the container image:

$ docker load -i mysql_image_backup.tar

Above, we restore the container image from the backup:

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED         SIZE
mysql        5.7       5107333e08a8   15 months ago   501MB

In this output, let’s ensure the image mysql:5.7 is now listed.

4.2. Extracting the Restoration Details From the Metadata

Here, we extract all the necessary information from the metadata file mysql_metadata.json instead of manually specifying the environment variables, volume mappings, and other settings:

# Extract container name
CONTAINER_NAME=$(jq -r '.[0].Name' mysql_metadata.json)

# Extract image name
IMAGE_NAME=$(jq -r '.[0].Config.Image' mysql_metadata.json)

# Extract volume mappings
VOLUME_MAPPINGS=$(jq -r '.[0].Mounts | map("-v \(.Name):\(.Destination)") | join(" ")' mysql_metadata.json)

# Extract environment variables
ENV_VARS=$(jq -r '.[0].Config.Env | map("-e " + .) | join(" ")' mysql_metadata.json)

To dynamically construct the docker run command, we use the jq command to extract:

  • CONTAINER_NAME – to set the correct name of the container
  • IMAGE_NAME – to ensure we use the correct MySQL image
  • VOLUME_MAPPINGS – to mount the data in the correct location
  • ENV_VARS – to restore MySQL credentials and database configuration

With this addition, we can restore the container with it’s original settings without manually specifying them.

4.3. Recreate the Data Volume

Next, we recreate the volume mysql_data:

$ docker volume create mysql_data
mysql_data

After the command completes, we can start a new MySQL container.

4.4. Restore the Volume Data

Now, let’s restore the MySQL database by extracting it from the backup:

$ docker run --rm -v mysql_data:/data -v $(pwd):/backup busybox tar xvf /backup/mysql_data_backup.tar -C /

This command copies the backup data in the new volume.

4.5. Restoring Metadata and Recreating the Container

To restore the container’s metadata, let’s dynamically reconstruct the docker run command using the JSON file mysql_metadata.json:

$ RESTORE_CMD="docker run --name $CONTAINER_NAME $ENV_VARS $VOLUME_MAPPINGS -d $IMAGE_NAME"

Let’s have a look at the breakdown:

  • jq -r ‘.[0] …’ mysql_metadata.json – reads the first container entry from mysql_metadata.json
  • docker run –name \( .Name ) – specifies the container name
  • if .Config.Env … end – adds environment variables if they exist
  • -v mysql_data:/var/lib/mysql – mounts the MySQL volume
  • -d ” + .Config.Image’ – sets the image to use

Let’s now execute the dynamically generated docker run command above stored in the RESTORE_CMD variable:

$ eval "$RESTORE_CMD"

We can now recreate our MySQL container with its original settings.

4.6. Restart the Container

Finally, let’s restart the container:

$ docker restart mysql_container
mysql_container

At this point, we confirm the Docker container backup and its associated data volume backup work correctly.

4.7. Automating the Restoration Process

Similarly, let’s automate the restoration process:

#!/bin/bash

# Backup filenames
IMAGE_BACKUP="mysql_image_backup.tar"
VOLUME_BACKUP="mysql_data_backup.tar"
METADATA_BACKUP="mysql_metadata.json"

echo "Starting restoration process..."

# Extract container name
CONTAINER_NAME=$(jq -r '.[0].Name' "$METADATA_BACKUP")

# Extract image name
IMAGE_NAME=$(jq -r '.[0].Config.Image' "$METADATA_BACKUP")

# Extract volume mappings
VOLUME_MAPPINGS=$(jq -r '.[0].Mounts | map("-v \(.Name):\(.Destination)") | join(" ")' "$METADATA_BACKUP")

# Extract environment variables
ENV_VARS=$(jq -r '.[0].Config.Env | map("-e " + .) | join(" ")' "$METADATA_BACKUP")

# Stop and remove existing container
if docker ps -a --format '{{.Names}}' | grep -q "^$CONTAINER_NAME$"; then
    echo "Stopping and removing existing container..."
    docker stop "$CONTAINER_NAME"
    docker rm "$CONTAINER_NAME"
fi

# Remove existing volume
for VOL in $(docker volume ls --format '{{.Name}}'); do
    if echo "$VOLUME_MAPPINGS" | grep -q "$VOL"; then
        echo "Removing existing volume: $VOL"
        docker volume rm "$VOL"
    fi
done

# Remove existing image
if docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "^$IMAGE_NAME$"; then
    echo "Removing existing image..."
    docker rmi "$IMAGE_NAME"
fi

# Restore the Docker image
echo "Loading Docker image..."
docker load -i "$IMAGE_BACKUP"

# Recreate the volumes
for VOL in $VOLUME_MAPPINGS; do
    VOL_NAME=$(echo "$VOL" | cut -d ':' -f1 | sed 's/-v //')
    echo "Recreating volume: $VOL_NAME"
    docker volume create "$VOL_NAME"
done

# Restore the volume data
echo "Restoring volume data..."
docker run --rm $VOLUME_MAPPINGS -v "$(pwd)":/backup busybox tar xvf "/backup/$VOLUME_BACKUP" -C /

# Construct the docker run command
RESTORE_CMD="docker run --name $CONTAINER_NAME $ENV_VARS $VOLUME_MAPPINGS -d $IMAGE_NAME"

# Recreate the container with original metadata
echo "Recreating container..."
eval "$RESTORE_CMD"

# Restart the container
echo "Restarting container..."
docker restart "$CONTAINER_NAME"

echo "Restoration completed successfully!"

Then, we can make the script executable:

$ chmod +x restoration_script.sh

Finally, let’s execute the script:

$ ./restoration_script.sh
Starting restoration process...
Stopping and removing existing container...
mysql_container
mysql_container
Starting restoration process...
Removing existing volume...
mysql_data
Removing existing image...
...
Loading Docker image...
...
Loaded image: mysql:5.7
Recreating volume...
mysql_data
Restoring volume data...
...
Extracting container metadata...
Recreating container...
4dc2f1513734905c6fad0f49748caf45f2cbdc02cf83e6de2060b0df2702fe5e
Restarting container...
mysql_container
Restoration completed successfully!

With the help of this automation script, we streamline how the existing resources are removed, as well as the restoration process.

5. Conclusion

In this article, we demonstrated how to create a backup for a MySQL container and its associated data volume.

For system failures or accidental deletions, backing up and restoring Docker containers, along with their data volumes, ensures smooth recovery and data safety. First, we created a MySQL container with a named volume. Next, we separately backed up both the container image and its volume. Before the restoration, we removed the existing resources. After that, we reloaded the image, recreated the volume and container, restored the database files, and finally restarted the container.

As a result, we enhance the management of our Docker container backups, ensuring a seamless restoration process.