TekOnline

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?

  1. Check if you’re using the correct volume name in both files
  2. Verify the database path in your environment variables
  3. Most likely cause: You’re using bind mounts instead of named volumes
  4. Check Drone CI logs for volume mounting errors
  5. Verify that your volume is actually a named volume, not a bind mount

Volume Not Mounting?

  1. Verify the volume is defined in both docker-compose.yml and .drone.yml
  2. Check that the paths match between your local setup and Drone configuration
  3. Ensure the volume driver is specified (driver: local)

Summary

Database persistence in Drone CI deployments requires:

  1. Named volumes instead of bind mounts (this is the key!)
  2. Proper Drone CI volume configuration
  3. Consistent database paths
  4. 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.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *