A Lightning-Fast Time Machine for Docker Volumes
Instantly snapshot and restore database states in <500ms
db-rewind is a high-performance CLI tool that acts as a "Time Machine" for local Docker volumes. It eliminates the "feedback loop latency" of re-seeding databases after destructive tests by leveraging Linux kernel syscalls for instant snapshots and atomic restores.
- π Sub-second performance - Snapshot 100GB databases in <500ms
- π Zero data loss - Atomic operations prevent corruption
- πΎ Space efficient - Copy-on-Write shares data blocks
- π― Database agnostic - Works with PostgreSQL, MySQL, MongoDB, etc.
- π‘οΈ Safe by default - Auto-pauses containers during operations
db-rewind operates through four distinct, optimized phases:
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β 1. INSPECT β βββ> β 2. FREEZE β βββ> β 3. SNAPSHOT β βββ> β 4. RESTORE β
β Docker Daemon β β Pause Process β β CoW Reflink β β Atomic Swap β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
- Connects to Docker daemon via Unix socket
- Inspects container to locate volume mount points
- Resolves host filesystem path for operations
- Pauses container using Docker API
- Flushes in-flight writes to disk
- Prevents data corruption during operations
- Uses
ioctl_ficlonesyscall for Copy-on-Write - Creates new inodes pointing to same data blocks
- O(1) time complexity - instant regardless of size
- Uses
renameat2(RENAME_EXCHANGE)syscall - Atomically swaps snapshot with active volume
- No intermediate state - zero downtime
How it works:
- Original volume data sits on disk blocks
- Reflink creates new inode with metadata only
- Both inodes point to same physical data blocks
- Data copied only when modified (copy-on-write)
Benefits:
- Near-instant snapshot creation
- Minimal disk space usage
- No performance degradation
Syscall Used:
ioctl(dest_fd, FICLONE, src_fd)How it works:
- Active volume and snapshot exist as separate directories
renameat2()swaps both directories atomically- Single syscall - no intermediate state
- Database never sees empty or partial state
Syscall Used:
renameat2(AT_FDCWD, path_a, AT_FDCWD, path_b, RENAME_EXCHANGE)db-rewind/
βββ cmd/ # CLI Commands
β βββ root.go # Base command & Docker integration
β βββ snapshot.go # Snapshot creation logic
β βββ restore.go # Restore operation logic
βββ internal/platform/ # Platform-specific code
β βββ container/
β β βββ docker.go # Docker API wrapper
β βββ fs/
β βββ cow.go # Copy-on-Write implementation
β βββ swap.go # Atomic swap implementation
βββ docs/ # Documentation & diagrams
βββ main.go # Application entry point
βββ Makefile # Build automation
βββ .golangci.yml # Linter configuration
βββ go.mod # Go module definition
| Requirement | Details |
|---|---|
| OS | Linux (kernel 3.15+ for renameat2) |
| Filesystem | Btrfs, XFS (with reflink support) |
| Docker | Installed and running |
| Permissions | Root/sudo (for /var/lib/docker access) |
| Go | 1.18+ (for building from source) |
# Check if your filesystem supports reflinks
cp --reflink=always /bin/bash /tmp/test_reflink 2>&1 | grep -q "Operation not supported" && echo "β No reflink support" || echo "β
Reflinks supported"git clone https://github.com/fa-anony-mous/db-rewind.git
cd db-rewind
make build# Install linting/formatting tools
make install-tools
# Format code
make fmt
# Run linters
make lint
# Build binary
make buildgo build -o db-rewind .# 1. Start a database container
docker run -d --name my-postgres \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:15
# 2. Load your data (migrations, seeds, etc.)
psql -h localhost -U postgres -c "CREATE TABLE users (id SERIAL, name TEXT);"
psql -h localhost -U postgres -c "INSERT INTO users (name) VALUES ('Alice'), ('Bob');"
# 3. Create a snapshot
sudo ./db-rewind snapshot my-postgres clean_state
# Output: β Snapshot created in 247ms
# 4. Run destructive tests
psql -h localhost -U postgres -c "DROP TABLE users;"
# 5. Restore instantly
sudo ./db-rewind restore my-postgres clean_state
# Output: β Restore complete in 89ms
# Data is back! πsudo ./db-rewind snapshot <container_name> <snapshot_name>Example:
sudo ./db-rewind snapshot my-postgres initial_seedsudo ./db-rewind restore <container_name> <snapshot_name>Example:
sudo ./db-rewind restore my-postgres initial_seedsudo ./db-rewind <container_name>Shows the resolved volume path.
# Before test suite
sudo ./db-rewind snapshot test-db baseline
# Run tests...
npm test
# After tests
sudo ./db-rewind restore test-db baseline# Save clean state after migrations
sudo ./db-rewind snapshot dev-postgres migrated
# Experiment with data...
# Made a mistake? Restore instantly
sudo ./db-rewind restore dev-postgres migratedsudo ./db-rewind snapshot my-db state_a
# Modify data...
sudo ./db-rewind snapshot my-db state_b
# Toggle between states instantly
sudo ./db-rewind restore my-db state_a# Format code
make fmt
# Run linters
make lint
# Run tests
make test
# Full build pipeline
make all- Linting:
golangci-lintwith strict rules - Formatting:
gofmt+goimports - Code Style: Clean, minimal comments, self-documenting
- Error Handling: Wrapped errors with context
| Operation | Database Size | Time | Notes |
|---|---|---|---|
| Snapshot | 1 GB | ~200ms | CoW - constant time |
| Snapshot | 10 GB | ~250ms | Independent of size |
| Snapshot | 100 GB | ~300ms | Metadata-only operation |
| Restore | Any size | ~100ms | Single syscall |
Benchmarks on Btrfs, NVMe SSD, Ryzen 5
When you restore a snapshot, the tool performs an atomic swap:
- The snapshot becomes the active volume
- The dirty volume becomes the snapshot
This means snapshots are "consumed" during restore. To preserve a clean state:
# Create a backup snapshot
sudo ./db-rewind snapshot my-db clean_state
sudo ./db-rewind snapshot my-db clean_state_backup
# Restore (consumes clean_state)
sudo ./db-rewind restore my-db clean_state
# You still have clean_state_backup for future restores- Btrfs: Full support β
- XFS: Requires kernel 4.5+ and reflink enabled β
- ext4: Not supported β
- NTFS: Not supported β
- Requires root/sudo privileges
- Only use in development environments
- Not intended for production backups
Contributions welcome! Please ensure:
- Code passes
make lint - Code is formatted with
make fmt - Commit messages are clear and descriptive
MIT License - see LICENSE for details.
Inspired by the need for faster development feedback loops and built with:
- Cobra - CLI framework
- Docker SDK - Container integration
- Go sys/unix - Low-level syscalls
Built with β‘ by developers, for developers


