Infrastructure

How to Deploy Litestream Using Docker Compose Behind Caddy

Litestream is a powerful, standalone streaming replication tool for SQLite. Learn how to deploy it alongside your web applications using Docker Compose and a Caddy reverse proxy.

Rajasekhar Gundala··5 min read

Litestream is a standalone streaming replication tool for SQLite. It runs as a background process and continuously streams changes from your SQLite databases to external storage like AWS S3, Cloudflare R2, or MinIO. It provides robust disaster recovery for single-node architectures without the overhead of a traditional database server.

Welcome back to another infrastructure guide! As single-node deployments and SQLite gain immense popularity, ensuring your data is safely backed up is more critical than ever.

Today, I will walk you through deploying a web application backed by SQLite, replicated by Litestream, and served securely behind a Caddy reverse proxy—all orchestrated with Docker Compose.

Prerequisites

Ensure you fulfill the following requirements before proceeding with the deployment:

  1. Ubuntu Server (or your preferred Linux distribution).
  2. Docker and Docker Compose installed on your server.
  3. An S3-compatible storage bucket (AWS S3, DigitalOcean Spaces, Cloudflare R2, etc.) and its associated access keys.

Introduction to Litestream

Litestream bridges the gap between the simplicity of SQLite and the durability of traditional client-server databases like PostgreSQL. By operating at the WAL (Write-Ahead Log) level, it replicates database changes in real-time.

While Litestream itself doesn’t serve HTTP traffic, it runs directly alongside your web application. Caddy acts as the front door, terminating SSL and routing traffic to your app, while Litestream works invisibly in the background to ensure your application’s SQLite database is never lost.

Litestream Core Features

Continuous Streaming
  • Replicates SQLite WAL frames to your storage bucket every second, ensuring near-zero data loss.
Point-in-Time Restore
  • Allows you to restore your database to any specific second in the past, protecting against accidental deletions or bad migrations.
Extremely Low Overhead
  • Consumes minimal CPU and memory, making it perfect for running on small VPS instances.
S3 Compatibility
  • Natively supports AWS S3, Google Cloud Storage, Azure Blob Storage, SFTP, and any S3-compatible API like MinIO or Cloudflare R2.

Prepare the Server Environment

Create a dedicated directory under /opt on your host to persist your application data and configurations:

cd /opt
sudo mkdir -p myapp/data
cd myapp

Litestream Configuration

Litestream requires a simple YAML file to map your local database to its remote storage destination.

Save the following configuration inside /opt/myapp/litestream.yml:

dbs:
  - path: /data/database.sqlite
    replicas:
      - url: s3://[my-litestream-bucket.us-east-1.amazonaws.com/myapp-db](https://my-litestream-bucket.us-east-1.amazonaws.com/myapp-db)
        access_key_id: ${LITESTREAM_ACCESS_KEY_ID}
        secret_access_key: ${LITESTREAM_SECRET_ACCESS_KEY}
We are using environment variables (${...}) for the credentials. This ensures your secrets stay out of your configuration files and are passed securely via Docker Compose.

Caddy Configuration (Caddyfile)

Create your Caddyfile inside /opt/myapp:

sudo touch Caddyfile
sudo nano Caddyfile

Paste the configuration below:

{
    email your-email@example.com
    cert_issuer acme

    servers {
        protocol h1 h2c h3
        strict_sni_host on
    }
}

app.example.com {
    log {
        output file /var/log/caddy/app.log
        format console
    }

    # Route traffic to your actual web application
    reverse_proxy app:8080 {
        transport http {
            keepalive 300s
        }
    }
}

Docker Compose Configuration

To run Litestream effectively in Docker, the “sidecar” pattern is highly recommended. The application and Litestream run as separate containers but share a Docker volume containing the SQLite database.

Create /opt/myapp/docker-compose.yml:

sudo touch docker-compose.yml
sudo nano docker-compose.yml

Add the following stack definition:

networks:
  caddy_net:
    driver: bridge

volumes:
  caddy_data:
  caddy_config:
  app_data: # Shared volume for the SQLite database

services:
  caddy:
    image: rajaseg/caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    networks:
      - caddy_net
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

  # Your Web Application
  app:
    image: your-web-app-image:latest # Replace with your actual app image
    container_name: app
    restart: unless-stopped
    networks:
      - caddy_net
    volumes:
      - app_data:/data # Mounts the shared volume to /data
    environment:
      - DATABASE_URL=sqlite:///data/database.sqlite

  # Litestream Sidecar
  litestream:
    image: litestream/litestream:latest
    container_name: litestream
    restart: unless-stopped
    depends_on:
      - app
    volumes:
      - ./litestream.yml:/etc/litestream.yml:ro
      - app_data:/data # Shares the EXACT same volume as the app
    environment:
      - LITESTREAM_ACCESS_KEY_ID=your_aws_or_r2_access_key
      - LITESTREAM_SECRET_ACCESS_KEY=your_aws_or_r2_secret_key
    command: replicate -config /etc/litestream.yml

Key configuration details used:

  • Shared Volume (app_data): Both the app and litestream containers mount the exact same volume. This allows Litestream to read the SQLite WAL files generated by your application in real-time.
  • Sidecar Pattern: By keeping Litestream in its own container, you don’t need to modify your application’s Dockerfile or use complex startup scripts.

Deploy the Stack

With your configuration files in place, deploy the stack in detached mode:

docker compose up -d

Litestream will automatically detect the database.sqlite file created by your application in the shared app_data volume and immediately begin replicating the WAL frames to your S3 bucket.

Restoring Your Database

If disaster strikes and you need to restore your database, you can use the Litestream container to pull the latest state from your bucket before starting your application.

Run these commands to safely restore the database from S3 into your shared Docker volume:

# 1. Stop the application container to prevent database locks
docker compose stop app

# 2. Run the restore command via the Litestream container
docker compose run --rm litestream restore -v -config /etc/litestream.yml /data/database.sqlite

# 3. Restart the application
docker compose start app
Always stop the application container before running a restore command to prevent SQLite database corruption via simultaneous writes.

Conclusion

By running Litestream as a sidecar to your main application container, you decouple your disaster recovery process from your application logic. Caddy handles the secure routing, your app serves the business logic, and Litestream quietly ensures your SQLite data is immortalized in the cloud.

Feel free to leave a comment below if you have any questions or encounter issues setting this up.

Share
Written by
Rajasekhar Gundala

Senior Infrastructure & Web Platform Leader.

Continue reading

Weekly Engineering Notes.

A weekly digest on infrastructure, observability, Rust, and the open web. No spam, just technical signals.

Free. Unsubscribe in one click.