Data Survival: Beyond the Container

Using a Volume is step one. But what if the host disk fails? What if you accidentally delete the volume? True persistence requires a Backup Strategy.

1. The “fsync” Promise

Databases (Postgres, MySQL) are designed to be durable (ACID). They use a system call named fsync() to force the OS to flush data from RAM to the physical disk platter.

  • Docker Volume: Respects fsync. When Postgres thinks data is safe, it is safe on the host disk.
  • Container Layer: Overlay2 also respects fsync, but with a massive performance penalty due to CoW.

[!IMPORTANT] Backup Rule: A volume on the host is still a single point of failure. You must export it off-site (S3, NAS).


2. Backup Strategies

1. Stop and Copy (Cold Backup)

The safest method.

  1. Stop the container (docker stop db).
  2. Tar the volume directory.
  3. Start the container.

2. Volume Mounting (Hot/Warm Backup)

Run a temporary container that mounts the volume and a backup directory. docker run --rm -v db-data:/data -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /data

3. Database Dump (Logical Backup)

Use the database’s own tools (pg_dump, mysqldump). This is often better than raw file copies because it ensures consistency without stopping the database.


3. Interactive: Data Survival Lab

Simulate a catastrophe and see if your data survives.

System Normal
Data: Safe
Waiting...

4. Code Example: Automating Backups

package main

import (
    "os/exec"
    "fmt"
)

func main() {
    // Hot Backup Strategy:
    // Run a temporary container to tar the volume content
    volumeName := "db-data"
    backupFile := "/backups/db-snap.tar"

    cmd := exec.Command("docker", "run", "--rm",
        "-v", volumeName + ":/data",
        "-v", "/backups:/backup",
        "alpine", "tar", "cf", "/backup/db-snap.tar", "/data")

    output, err := cmd.CombinedOutput()
    if err != nil {
        fmt.Printf("Backup Failed: %s\n", string(output))
    } else {
        fmt.Printf("Backup Success: %s created\n", backupFile)
    }
}
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class BackupManager {
    public static void main(String[] args) {
        try {
            // Equivalent to: docker run --rm ...
            ProcessBuilder pb = new ProcessBuilder(
                "docker", "run", "--rm",
                "-v", "db-data:/data",
                "-v", "/backups:/backup",
                "alpine", "tar", "cf", "/backup/db-snap.tar", "/data"
            );

            Process p = pb.start();
            int exitCode = p.waitFor();

            if (exitCode == 0) {
                System.out.println("Backup complete.");
            } else {
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(p.getErrorStream()));
                System.out.println("Error: " + reader.readLine());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}