S3 Cloud Backup Configuration
Duckling supports automated S3 cloud backups for disaster recovery. Production DuckDB replicas can reach 200+ GB, making a full resync from MySQL take hours. S3 backups enable fast recovery by downloading a pre-built.db file instead of re-streaming from MySQL.
Overview
S3 backup configuration is per-database and stored indatabases.json. Backups can be triggered manually or run automatically on a schedule.
Key Features:
- Per-database S3 configuration
- Four encryption modes: none, SSE-S3, SSE-KMS, client-side AES-256
- Streaming upload (no temp files, works for 500+ GB databases)
- HMAC integrity verification (client-side encryption only)
- S3-compatible providers supported (MinIO, R2, B2, Spaces)
Environment Variables
S3 credentials are stored per-database indatabases.json (not environment variables).
Global automation settings:
Enable automatic backups (local + S3 if configured)
Hours between automatic backups (applies to both local and S3)
Days to retain local backups (S3 retention managed via lifecycle policies)
S3 Configuration Schema
S3 config is stored inside each database entry indatabases.json:
Required Fields
Enable S3 backups for this database
S3 bucket name (e.g.,
my-duckling-backups)AWS region (e.g.,
us-east-1) or region for S3-compatible providerAWS access key ID or equivalent for S3-compatible provider
AWS secret access key (stored in
databases.json, masked in API responses)Optional Fields
Custom S3 endpoint for S3-compatible providers (MinIO, R2, B2, Spaces). Leave blank for AWS S3.
Use path-style URLs (e.g.,
https://endpoint/bucket/key). Required for MinIO and most self-hosted providers.S3 key prefix for backups (e.g.,
production/ → production/backup-2025-03-01T12-00-00-000Z.db)Encryption mode (see Encryption Modes section)
KMS key ARN for
sse-kms mode (optional, uses default KMS key if omitted)64-character hex string (32 bytes / 256 bits) for
client-aes256 mode. Generate with:Encryption Modes
Duckling supports four encryption modes for S3 backups:| Mode | Who Holds Key | Protects Against | Overhead |
|---|---|---|---|
| none | — | Nothing | Zero |
| sse-s3 | AWS (managed) | Physical media theft | Zero |
| sse-kms | AWS KMS | Physical media theft + audit trail | ~1 ms/request |
| client-aes256 | You (in databases.json) | Compromised AWS credentials, bucket misconfiguration | Streaming, no memory spike |
Encryption Mode: none
No encryption. Backup uploaded as plaintext.
Encryption Mode: sse-s3
Server-side encryption using AWS-managed keys (AES-256).
- Duckling sends
ServerSideEncryption: AES256header - AWS encrypts data at rest using AWS-managed keys
- AWS decrypts automatically on download
Encryption Mode: sse-kms
Server-side encryption using AWS KMS (Key Management Service).
- Duckling sends
ServerSideEncryption: aws:kms+SSEKMSKeyIdheader - AWS encrypts data using specified KMS key
- AWS decrypts automatically on download (requires KMS permissions)
- Audit trail (CloudTrail logs all KMS API calls)
- Key rotation policies
- Fine-grained access control (IAM policies)
Encryption Mode: client-aes256
Recommended for production. Client-side encryption using AES-256-CTR before upload.
- Generate random 16-byte IV (initialization vector)
- Encrypt database file using AES-256-CTR (streaming)
- Compute HMAC-SHA256 over IV + ciphertext
- Upload encrypted file to S3 (format:
[16-byte IV][ciphertext]) - Upload HMAC to companion
.macobject
- Download encrypted file +
.maccompanion - Compute HMAC and verify against stored value
- Decrypt file using AES-256-CTR (streaming)
- Replace primary DuckDB file
- Zero Trust: AWS never sees your data in plaintext
- Bucket Misconfiguration Protection: Even if bucket is public, data is encrypted
- Compromised Credentials: Attackers can’t read data without encryption key
- Streaming: No memory spike (handles 500+ GB databases)
S3-Compatible Providers
Duckling supports S3-compatible object storage providers:| Provider | endpoint | forcePathStyle |
|---|---|---|
| AWS S3 | (leave blank) | false |
| Cloudflare R2 | https://<account_id>.r2.cloudflarestorage.com | false |
| Backblaze B2 | https://s3.<region>.backblazeb2.com | false |
| DigitalOcean Spaces | https://<region>.digitaloceanspaces.com | false |
| MinIO (self-hosted) | https://minio.internal:9000 | true |
Example: Cloudflare R2
Example: MinIO
Backup File Format
Unencrypted Backups
Standard DuckDB.db file:
Client-Side Encrypted Backups
Encrypted file + companion HMAC:.mac file contains the HMAC hex string for integrity verification.
Automated Backups
WhenAUTO_BACKUP=true and s3.enabled=true, Duckling automatically:
- Creates local backup every
BACKUP_INTERVAL_HOURShours - Uploads local backup to S3
- Deletes local backup after
BACKUP_RETENTION_DAYSdays
Manual Backups
Trigger S3 Backup
List Backups
Restore from S3
Restore Specific Backup
- Stop Sync: Halt all sync/CDC operations
- Download: Stream encrypted backup from S3 to temp file
- Verify HMAC: Check integrity (client-side encryption only)
- Decrypt: Stream-decrypt to final location (client-side encryption only)
- Replace: Atomic rename to replace primary DuckDB file
- Restart Sync: Resume sync/CDC operations
Monitoring
Test S3 Connection
Backup Statistics
Check automation service status:Performance Characteristics
Upload Performance
| Database Size | Upload Time (100 Mbps) | Upload Time (1 Gbps) | Memory Usage |
|---|---|---|---|
| 10 GB | ~15 minutes | ~2 minutes | ~50 MB |
| 100 GB | ~2.5 hours | ~15 minutes | ~50 MB |
| 200 GB | ~5 hours | ~30 minutes | ~50 MB |
| 500 GB | ~12 hours | ~1.5 hours | ~50 MB |
Restore Performance
| Database Size | Download Time (100 Mbps) | Decrypt Time (SSD) | Total Restore Time | |---------------|--------------------------|--------------------|--------------------|| | 10 GB | ~15 minutes | ~30 seconds | ~16 minutes | | 100 GB | ~2.5 hours | ~5 minutes | ~2.6 hours | | 200 GB | ~5 hours | ~10 minutes | ~5.2 hours | | 500 GB | ~12 hours | ~25 minutes | ~12.5 hours | Download + Decrypt: Restore time = download time + decrypt time (decryption is fast compared to network transfer).Troubleshooting
Upload Failures
Error:Access Denied
IAM policy missing permissions:
No Such Bucket
Bucket doesn’t exist or wrong region:
Restore Failures
Error:HMAC verification failed
Backup corrupted or wrong encryption key:
- Verify encryption key matches key used during upload
- Re-upload backup (previous upload may have failed)
- Check network integrity (S3 transfer corruption)
No space left on device
Insufficient disk space for restore:
Code Reference
Implementation:packages/server/src/services/s3BackupService.ts
Key Methods:
uploadBackup()- Upload backup to S3 (line 86)downloadBackup()- Restore from S3 (line 176)listBackups()- List S3 backups (line 56)deleteBackup()- Delete S3 backup (line 293)testConnection()- Test S3 connectivity (line 51)uploadEncrypted()- Client-side encryption upload (line 131)downloadAndDecrypt()- Verify HMAC + decrypt (line 198)
parseEncryptionKey()- Validate 64-char hex key (line 43)uploadPlain()- Unencrypted or SSE upload (line 106)