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.
- Stop the container (
docker stop db). - Tar the volume directory.
- 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.
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();
}
}
}