Skip to content

Deploy with Docker

The fastest way to get Quackback into production. Docker handles dependencies, database setup, and migrations automatically.

Prerequisites

  • Docker Engine 20.10+
  • Docker Compose v2+
  • 1 GB RAM minimum (2 GB recommended)
  • PostgreSQL 18+ (included in docker-compose, or bring your own)

Quick Deploy

[Server]

# Clone the repository
git clone https://github.com/quackbackio/quackback.git
cd quackback
 
# Configure environment
cp .env.example .env
# Edit .env with your settings (see Configuration below)
 
# Start services
docker compose up -d

Use Docker Run

docker run -d \
  --name quackback \
  -p 3000:3000 \
  -e DATABASE_URL="postgresql://user:pass@host:5432/quackback" \
  -e REDIS_URL="redis://your-redis-host:6379" \
  -e SECRET_KEY="your-32-char-secret" \
  -e BASE_URL="https://feedback.yourcompany.com" \
  ghcr.io/quackbackio/quackback:latest

How the Docker Image Works

The Quackback Docker image uses a multi-stage build:

  • Builder stage compiles the application and installs dependencies
  • Production stage runs as a non-root quackback user for security
  • Entrypoint (docker-entrypoint.sh) runs database migrations automatically on every startup, then starts the server
  • Optional seeding with SEED_DATABASE=true to populate demo data
  • Health endpoint at /api/health

Because migrations run on every startup, upgrading is as simple as pulling the latest image and restarting. No manual migration step needed.

Configuration

Required Environment Variables

# Database connection
DATABASE_URL="postgresql://postgres:postgres@postgres:5432/quackback"
 
# Redis/Dragonfly connection for background job queue (BullMQ)
REDIS_URL="redis://localhost:6379"
 
# Authentication (generate with: openssl rand -base64 32)
SECRET_KEY="your-32-character-minimum-secret-key"
 
# Public URL (must match your domain)
BASE_URL="https://feedback.yourcompany.com"

Email Configuration

Without email configuration, OTP codes are printed to the console. For production, configure one of the providers below.

SMTP (Recommended)

EMAIL_SMTP_HOST="smtp.example.com"
EMAIL_SMTP_PORT="587"
EMAIL_SMTP_USER="your-username"
EMAIL_SMTP_PASS="your-password"
EMAIL_FROM="feedback@yourcompany.com"

Resend

EMAIL_RESEND_API_KEY="re_xxxxxxxxxxxx"
EMAIL_FROM="feedback@yourcompany.com"

OAuth Providers (Optional)

# GitHub
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"
 
# Google
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"

Integrations (Optional)

# Slack
SLACK_CLIENT_ID="your-slack-client-id"
SLACK_CLIENT_SECRET="your-slack-client-secret"

See Environment Variables Reference for all options.

Docker Compose Configuration

The included docker-compose.yml uses a custom PostgreSQL image with required extensions, plus MinIO for file storage and Dragonfly for the background job queue:

services:
  postgres:
    build:
      context: ./docker/postgres
      dockerfile: Dockerfile
    container_name: quackback-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: quackback
    ports:
      - '5432:5432'
    volumes:
      - postgres_data:/var/lib/postgresql
      - ./docker/postgres/init-pgcron.sql:/docker-entrypoint-initdb.d/init-pgcron.sql
    command: postgres -c shared_preload_libraries=pg_cron -c cron.database_name=quackback -c max_connections=200
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U postgres']
      interval: 5s
      timeout: 5s
      retries: 5
 
  minio:
    image: minio/minio:latest
    container_name: quackback-minio
    restart: unless-stopped
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
    ports:
      - '9000:9000'
      - '9001:9001'
    volumes:
      - minio_data:/data
    command: server /data --console-address ":9001"
 
  dragonfly:
    image: docker.dragonflydb.io/dragonflydb/dragonfly:v1.27.1
    container_name: quackback-dragonfly
    restart: unless-stopped
    command: dragonfly --cluster_mode=emulated --lock_on_hashtags
    ports:
      - '6379:6379'
    volumes:
      - dragonfly_data:/data
 
volumes:
  postgres_data:
  minio_data:
  dragonfly_data:

The custom Dockerfile (docker/postgres/Dockerfile) installs pg_cron and pgvector extensions on top of PostgreSQL 18. Dragonfly is a Redis-compatible in-memory store used by BullMQ for background job processing. MinIO provides S3-compatible file storage for image uploads.

Database Setup

Use the included PostgreSQL

The docker-compose setup includes PostgreSQL with the required extensions (pgvector, pg_cron). Migrations run automatically when the container starts.

Use an external database

[Database Provider] For managed databases (AWS RDS, Supabase, Neon, etc.):

  1. Create a PostgreSQL 18+ database
  2. Enable the pgvector extension
  3. Set DATABASE_URL in your .env

[Server] Migrations run automatically when the Docker container starts, so no manual migration step is needed. If you need to run migrations outside of Docker:

docker run --rm \
  -e DATABASE_URL="your-external-db-url" \
  ghcr.io/quackbackio/quackback:latest \
  bun run db:migrate

Reverse Proxy

In production, place a reverse proxy in front of Quackback for HTTPS. See the Reverse Proxy Configuration guide for Caddy, Nginx, and Traefik examples.

Upgrade

Always back up your database before upgrading.

# 1. Back up your database
docker compose exec postgres pg_dump -U postgres quackback > backup.sql
 
# 2. Pull the latest image
docker compose pull
 
# 3. Restart (migrations run automatically)
docker compose up -d

Drizzle migrations are sequential and safe to re-run, so the container handles schema updates on startup without any manual steps.

Rollback

If something goes wrong:

# Stop containers
docker compose down
 
# Restore the backup
docker compose up -d postgres
docker compose exec -T postgres psql -U postgres quackback < backup.sql
 
# Start with the previous image
docker compose up -d app

Health Checks

# Application health
curl http://localhost:3000/api/health

Troubleshooting

Container won't start

Check logs:

docker compose logs app

Database connection failed

  1. Verify PostgreSQL is healthy:

    docker compose ps
  2. Test connection:

    docker compose exec postgres psql -U postgres -c "SELECT 1"

Port already in use

# Find process using port 3000
lsof -i :3000
 
# Or change the port in docker-compose.yml
# ports:
#   - "8080:3000"

Security

Follow these practices for production deployments.

  1. Use strong secrets - Generate with openssl rand -base64 32
  2. Enable HTTPS - Always use TLS in production
  3. Restrict database access - Do not expose PostgreSQL publicly
  4. Back up regularly - Automate database backups
  5. Keep updated - Pull latest images regularly

Next Steps