Your cart is currently empty!
Avoiding Database Persistence Pitfalls in Drone CI Deployments
The Problem: Data Loss During Deployments
When deploying applications with Drone CI, one of the most frustrating issues is losing your database data after each deployment. You might see your user count reset to zero, or watch as all your carefully collected data disappears into the void.
The key insight: Data loss happens when you use bind mounts instead of named volumes. With proper named volumes, your data will survive even the most aggressive container recreation.
Why Data Loss Happens (and How to Prevent It)
1. Bind Mounts vs Named Volumes – The Critical Difference
❌ Bind Mounts (Data Loss Risk):
- Maps a local directory to the container
- In Drone CI, the local directory might not exist or be empty
- When containers are recreated, the bind mount points to nothing
- Result: Data disappears
✅ Named Volumes (Data Persistence):
- Managed by Docker, independent of containers
- Survives container recreation, deletion, and recreation
- Works consistently across all environments
- Result: Data persists even with
--force-recreate
2. The --force-recreate
Flag – It’s Not Always Evil
With bind mounts: --force-recreate
= guaranteed data loss With named volumes: --force-recreate
= safe, data survives
The flag itself isn’t the problem – it’s the combination of the flag with improper volume configuration.
The Solution: Proper Volume Management
Step 1: Use Named Volumes Instead of Bind Mounts
❌ Don’t do this (bind mount):
# docker-compose.yml
services:
app:
volumes:
# Bind mount - maps local directory to container
# Problem: In Drone CI, this path doesn't exist or is empty
- ./data:/app/data # This won't persist in Drone CI
✅ Do this (named volume):
# docker-compose.yml
services:
app:
volumes:
# Named volume - managed by Docker, persists across deployments
- app-db:/app/data # Named volume that persists
# Define the named volume
volumes:
app-db:
driver: local # Store data on local filesystem
Step 2: Configure Drone CI Volumes
❌ Don’t do this (bind mounts + force-recreate):
# .drone.yml
steps:
- name: deploy
image: docker/compose
commands:
# With bind mounts, --force-recreate WILL destroy your data
# Because the bind mount points to an empty/non-existent directory
- docker compose up -d --force-recreate # DANGEROUS with bind mounts
✅ Do this (named volumes – safe with force-recreate):
# .drone.yml
# Define persistent volumes at pipeline level
volumes:
# Volume for Docker socket (required for Docker commands)
- name: docker-socket
host:
path: /var/run/docker.sock # Host Docker socket
# Volume for database persistence
- name: app-db
host:
path: /var/lib/drone/app-db # Persistent storage location
steps:
- name: deploy
image: docker/compose
# Mount volumes into the deployment step
volumes:
- name: docker-socket
path: /var/run/docker.sock
- name: app-db
path: /drone/src/data # Maps to your local data directory
commands:
# With named volumes, --force-recreate is SAFE
# Your data will survive container recreation
- docker compose up -d --force-recreate # SAFE with named volumes
Step 3: Ensure Correct Database Path
Make sure your application’s database path matches the volume mount:
❌ Wrong path:
# .env.prod
# Database path doesn't match volume mount
# Container will create database in /app/database.db (not persistent)
DATABASE_URL=sqlite:///./database.db
✅ Correct path:
# .env.prod
# Database path matches volume mount in docker-compose.yml
# Container will use /app/data/database.db (persistent volume)
DATABASE_URL=sqlite:///./data/database.db
Complete Working Example
Here’s a complete setup that ensures database persistence:
docker-compose.yml
# Docker Compose configuration for production deployment
version: '3.8'
services:
# Main application service
app:
# Build the Docker image from the backend directory
build: ./backend
# Environment variables for the application
environment:
# Database connection string - points to the persistent volume
- DATABASE_URL=sqlite:///./data/app.db
# Volume mounts for persistent data
volumes:
# Named volume for database persistence (survives container restarts)
- app-db:/app/data
# Bind mount for uploads (user-uploaded files)
- ./backend/uploads:/app/uploads
# Bind mount for application logs
- ./backend/logs:/app/logs
# Port mapping: host port 8000 -> container port 8000
ports:
- "8000:8000"
# Define named volumes for persistent data storage
volumes:
# Named volume for database files
# This volume persists even when containers are recreated
app-db:
driver: local # Use local filesystem storage
.drone.yml
# Drone CI pipeline configuration for automated deployment
kind: pipeline
type: docker
name: deploy
# Define persistent volumes that survive across pipeline runs
volumes:
# Volume for Docker socket (required for Docker-in-Docker operations)
- name: docker-socket
host:
path: /var/run/docker.sock # Host Docker socket path
# Volume for database persistence across deployments
- name: app-db
host:
path: /var/lib/drone/app-db # Persistent storage on Drone host
# Pipeline steps (actions to execute)
steps:
# Deployment step
- name: deploy
# Use Docker Compose image for deployment
image: docker/compose
# Mount volumes into the deployment container
volumes:
# Mount Docker socket for Docker commands
- name: docker-socket
path: /var/run/docker.sock
# Mount database volume to match docker-compose.yml structure
# This path should match your local backend/data directory
- name: app-db
path: /drone/src/backend/data
# Commands to execute during deployment
commands:
# Stop existing containers gracefully
- docker compose down
# Start containers with latest code (rebuild if needed)
# With named volumes, --force-recreate is safe and ensures clean deployment
- docker compose up -d --build --force-recreate
.env.prod
# Production environment variables
# Database connection string for SQLite
# Format: sqlite:///./path/to/database.db
# The ./data/ part must match the volume mount in docker-compose.yml
DATABASE_URL=sqlite:///./data/app.db
Key Points to Remember
1. Named Volumes vs Bind Mounts
- Named volumes are managed by Docker and persist across deployments
- Bind mounts are relative to the host system and may not work in CI environments
2. Drone CI Volume Paths
- Drone mounts volumes relative to
/drone/src/
- Your volume path in
.drone.yml
should match your local directory structure
3. Understanding --force-recreate
- With bind mounts: This flag destroys your data
- With named volumes: This flag is safe, data survives
- The flag itself isn’t evil – it’s the volume configuration that matters
4. Database Path Consistency
- Ensure your
DATABASE_URL
points to the same path as your volume mount - Test locally first to verify the path works
Testing Your Setup
Before Deployment
# Check your database locally to establish baseline
# This shows how many users exist before deployment
docker compose exec app sqlite3 /app/data/app.db "SELECT COUNT(*) FROM users;"
After Deployment
# Check that data persists after Drone CI deployment
# If this number matches the "before" count, persistence is working
docker exec -it your-app-container sqlite3 /app/data/app.db "SELECT COUNT(*) FROM users;"
Common Pitfalls to Avoid
1. Mixing Volume Types
Don’t use bind mounts for databases in production. Stick to named volumes.
2. Incorrect Paths
Double-check that your database path in the environment variable matches your volume mount.
3. Forgetting to Rebuild
If you change environment variables, you need to rebuild the container:
docker compose up -d --build
4. Not Testing Locally
Always test your volume setup locally before deploying to ensure paths are correct.
Troubleshooting
Data Still Disappearing?
- Check if you’re using the correct volume name in both files
- Verify the database path in your environment variables
- Most likely cause: You’re using bind mounts instead of named volumes
- Check Drone CI logs for volume mounting errors
- Verify that your volume is actually a named volume, not a bind mount
Volume Not Mounting?
- Verify the volume is defined in both
docker-compose.yml
and.drone.yml
- Check that the paths match between your local setup and Drone configuration
- Ensure the volume driver is specified (
driver: local
)
Summary
Database persistence in Drone CI deployments requires:
- Named volumes instead of bind mounts (this is the key!)
- Proper Drone CI volume configuration
- Consistent database paths
- Understanding that
--force-recreate
is safe with named volumes
The Golden Rule: Use named volumes for your database, and you can use any Docker Compose flags you want without fear of data loss.
By following these guidelines, your data will survive deployments and you’ll have a reliable, persistent database setup. 🚀
This guide is based on real-world experience fixing database persistence issues in a FastAPI application deployed with Drone CI. The solution ensures that user data, application state, and all database records survive container restarts and deployments.
by
Tags:
Leave a Reply