How to Self-Host Convex with Dokploy or Docker Compose

Complete guide to self-hosting Convex backend on your own infrastructure using Dokploy or Docker Compose. Includes SQLite and PostgreSQL options.

How to Self-Host Convex with Dokploy or Docker Compose

If you’re building real-time applications with databases, you’ve probably heard of Convex. It’s a backend-as-a-service platform that combines a real-time database with serverless functions. Convex has a free tier for cloud hosting, but self-hosting gives you more control over your data, infrastructure, and costs for production workloads.

In this guide, I’ll walk you through self-hosting Convex using either Dokploy (simpler approach) or Docker Compose (more control). We’ll cover both SQLite deployments for smaller setups and PostgreSQL for production.

What is Convex?

Convex is a backend platform that combines databases, API servers, and caching layers into one system. It handles real-time data synchronization, serverless functions, and caching using a TypeScript API.

Key Features of Convex

  • Real-Time Database: Automatic data synchronization across all clients with reactive queries
  • TypeScript-First: End-to-end type safety from backend to frontend
  • Serverless Functions: Write queries, mutations, and actions in TypeScript without managing servers
  • Built-in Scheduling: Cron jobs and scheduled functions without external services
  • File Storage: Built-in file upload and storage with CDN distribution
  • Full-Text Search: Native search capabilities without Elasticsearch
  • Authentication: Flexible auth system supporting various providers
  • Atomic Transactions: ACID guarantees for data consistency
  • Time Travel: Query historical data and debug with time-travel queries
  • Vector Search: Built-in vector database for AI applications

Why Self-Host Convex?

Self-hosting advantages:

  • You own the data and control where it lives
  • No bandwidth or function execution caps
  • Customize infrastructure and scaling
  • Lower costs at scale
  • Run on-premises or in private networks

Cloud-Hosted Convex Free Tier:

  • 1GB storage
  • 1M function calls per month
  • Unlimited projects
  • Works well for development and small apps

Consider self-hosting when you hit those limits or need more infrastructure control.

Prerequisites

You’ll need:

  • A VPS or Server: 2GB RAM minimum, 2 CPU cores (4GB RAM recommended for PostgreSQL)
  • A Domain Name: For your Convex backend (e.g., api.yourdomain.com)
  • Docker Installed: Docker and Docker Compose (Dokploy includes Docker)
  • Basic Command Line Knowledge: For running commands
Try Hetzner Cloud Now

Hosting Recommendations

For development, a basic VPS with 2GB RAM works fine with SQLite. For production, use 4GB RAM with PostgreSQL hosted in the same region for optimal performance. Providers like Hetzner, DigitalOcean, or AWS work well.

Option 1: Deploy with Dokploy (Easiest Method)

Dokploy is an open-source Platform as a Service for deploying applications. Good news: Dokploy includes a Convex template. This means you can deploy Convex with a few clicks. If you haven’t set up Dokploy yet, check out our Dokploy Installation Guide.

This is the fastest way to deploy Convex. Dokploy has a built-in template that you can use as-is or customize with your own configuration.

Step 1: Install Dokploy (if not already installed)

curl -sSL https://dokploy.com/install.sh | sh

Access Dokploy at http://your-vps-ip:3000 and complete the setup.

Step 2: Deploy from Template

  1. Log in to Dokploy dashboard
  2. Click “Create Project” and name it (e.g., “Convex”)
  3. Click “Templates” in the left sidebar
  4. Search for “Convex” in the template gallery
  5. Click “Deploy” on the Convex template

Step 3: Customize the Configuration (Optional)

The template comes with a default configuration, but you can override it with your own settings:

  1. After deploying the template, go to the Docker Compose tab
  2. You’ll see the template’s default YAML - you can edit it if needed
  3. Go to the Environment tab to set your variables (see below)

Template Flexibility

The Dokploy template is a good starting point, but you can customize it by editing the Docker Compose configuration and environment variables.

services:
  backend:
    image: ghcr.io/get-convex/convex-backend:latest
    volumes:
      - data:/convex/data
    environment:
      - INSTANCE_NAME=${INSTANCE_NAME:-convex-self-hosted}
      - INSTANCE_SECRET=${INSTANCE_SECRET:-}
      - CONVEX_RELEASE_VERSION_DEV=${CONVEX_RELEASE_VERSION_DEV:-}
      - ACTIONS_USER_TIMEOUT_SECS=${ACTIONS_USER_TIMEOUT_SECS:-}
      - CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-http://127.0.0.1:3210}
      - CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://127.0.0.1:3211}
      - POSTGRES_URL=${POSTGRES_URL:-}
      - DISABLE_BEACON=${DISABLE_BEACON:-true}
      - REDACT_LOGS_TO_CLIENT=${REDACT_LOGS_TO_CLIENT:-}
      - RUST_LOG=${RUST_LOG:-info}
      - RUST_BACKTRACE=${RUST_BACKTRACE:-}
      - DO_NOT_REQUIRE_SSL=${DO_NOT_REQUIRE_SSL:-1}
    healthcheck:
      test: curl -f http://localhost:3210/version
      interval: 5s
      start_period: 5s

  dashboard:
    image: ghcr.io/get-convex/convex-dashboard:latest
    environment:
      - NEXT_PUBLIC_DEPLOYMENT_URL=${NEXT_PUBLIC_DEPLOYMENT_URL:-http://127.0.0.1:3210}
    depends_on:
      backend:
        condition: service_healthy

  postgres:
    image: postgres:17-alpine
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=convex_self_hosted
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  data:
  postgres-data:

SQLite vs PostgreSQL

This configuration includes PostgreSQL for production. If you prefer SQLite (simpler, works well for development), leave POSTGRES_URL empty and Convex will use SQLite. For production, set POSTGRES_URL to connect to PostgreSQL.

Step 4: Configure Environment Variables

Go to the Environment tab and add these variables:

Required:

  • INSTANCE_SECRET: Generate with openssl rand -hex 32

URLs - Choose one option:

Option A: Use Dokploy’s Free Traefik Domains (Quick start)

NEXT_PUBLIC_DEPLOYMENT_URL=http://shhosted-convex-cf33fb-91-98-95-196.traefik.me
CONVEX_CLOUD_ORIGIN=http://shhosted-convex-cf33fb-91-98-95-196.traefik.me
CONVEX_SITE_ORIGIN=http://shhosted-convex-59a34c-91-98-95-196.traefik.me

Option B: Use Your Own Domain (Production)

First, set up DNS A record: backend.convex.yourdomain.com → Your VPS IP

Then set:

CONVEX_CLOUD_ORIGIN=https://api.convex.yourdomain.com
CONVEX_SITE_ORIGIN=https://backend.convex.yourdomain.com
NEXT_PUBLIC_DEPLOYMENT_URL=https://api.convex.yourdomain.com

PostgreSQL (optional, leave empty to use SQLite):

DB_PASSWORD=your-strong-password
POSTGRES_URL=postgresql://postgres:your-strong-password@postgres:5432

Step 5: Configure Domain in Dokploy (if using custom domain)

  1. Go to the Domains tab
  2. Add your domain: backend.convex.yourdomain.com
  3. Dokploy’s Traefik will automatically handle SSL

Step 6: Deploy and Generate Admin Key

  1. Click “Deploy” and wait for services to start
  2. Once healthy, click “Terminal” to access the backend container
  3. Navigate and run:
cd convex
./generate_admin_key.sh
  1. Save the admin key securely

Option 2: Deploy with Docker Compose Only

If you prefer deploying without Dokploy or want more control, here’s how to use Docker Compose directly.

Step 1: Prepare Your Server

Update system and install Docker:

# Update packages
sudo apt update && sudo apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Install Docker Compose
sudo apt install docker-compose -y

Step 2: Create Project Directory

mkdir -p ~/convex-backend
cd ~/convex-backend

Step 3: Create Docker Compose File

For SQLite (Simple Setup):

nano docker-compose.yml

Paste:

services:
  backend:
    image: ghcr.io/get-convex/convex-backend:latest
    stop_grace_period: 10s
    stop_signal: SIGINT
    ports:
      - "3210:3210"
      - "3211:3211"
    volumes:
      - data:/convex/data
    environment:
      - INSTANCE_NAME=${INSTANCE_NAME:-convex-self-hosted}
      - INSTANCE_SECRET=${INSTANCE_SECRET}
      - CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-http://127.0.0.1:3210}
      - CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://127.0.0.1:3211}
      - RUST_LOG=${RUST_LOG:-info}
      - DISABLE_BEACON=${DISABLE_BEACON:-false}
    healthcheck:
      test: curl -f http://localhost:3210/version
      interval: 5s
      start_period: 10s

  dashboard:
    image: ghcr.io/get-convex/convex-dashboard:latest
    stop_grace_period: 10s
    stop_signal: SIGINT
    ports:
      - "6791:6791"
    environment:
      - NEXT_PUBLIC_DEPLOYMENT_URL=${NEXT_PUBLIC_DEPLOYMENT_URL:-http://127.0.0.1:3210}
    depends_on:
      backend:
        condition: service_healthy

volumes:
  data:

For PostgreSQL (Production Setup):

services:
  backend:
    image: ghcr.io/get-convex/convex-backend:latest
    stop_grace_period: 10s
    stop_signal: SIGINT
    ports:
      - "3210:3210"
      - "3211:3211"
    environment:
      - INSTANCE_NAME=${INSTANCE_NAME:-convex-self-hosted}
      - INSTANCE_SECRET=${INSTANCE_SECRET}
      - CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-http://127.0.0.1:3210}
      - CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://127.0.0.1:3211}
      - POSTGRES_URL=${POSTGRES_URL}
      - DO_NOT_REQUIRE_SSL=${DO_NOT_REQUIRE_SSL:-false}
      - RUST_LOG=${RUST_LOG:-info}
      - DOCUMENT_RETENTION_DELAY=${DOCUMENT_RETENTION_DELAY:-172800}
      - DISABLE_BEACON=${DISABLE_BEACON:-false}
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: curl -f http://localhost:3210/version
      interval: 5s
      start_period: 10s

  dashboard:
    image: ghcr.io/get-convex/convex-dashboard:latest
    stop_grace_period: 10s
    stop_signal: SIGINT
    ports:
      - "6791:6791"
    environment:
      - NEXT_PUBLIC_DEPLOYMENT_URL=${NEXT_PUBLIC_DEPLOYMENT_URL:-http://127.0.0.1:3210}
    depends_on:
      backend:
        condition: service_healthy

  postgres:
    image: postgres:17-alpine
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=convex_self_hosted
    volumes:
      - postgres-data:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres-data:

Step 4: Create Environment File

nano .env

Add your configuration:

# Instance Configuration
INSTANCE_NAME=convex-self-hosted
INSTANCE_SECRET=<generate-with-openssl-rand-hex-32>

# Public URLs (update these for production)
CONVEX_CLOUD_ORIGIN=http://127.0.0.1:3210
CONVEX_SITE_ORIGIN=http://127.0.0.1:3211
NEXT_PUBLIC_DEPLOYMENT_URL=http://127.0.0.1:3210

# PostgreSQL Configuration (only if using PostgreSQL)
DB_PASSWORD=<your-secure-db-password>
POSTGRES_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432
DO_NOT_REQUIRE_SSL=true

# Optional: Logging
RUST_LOG=info

# Optional: Disable telemetry
DISABLE_BEACON=false

Generate secrets:

# Generate instance secret
openssl rand -hex 32

Step 5: Start Convex

# Start services
docker-compose up -d

# View logs
docker-compose logs -f

# Check status
docker-compose ps

Step 6: Set Up Reverse Proxy with Nginx

For production with custom domains, set up Nginx:

sudo apt install nginx certbot python3-certbot-nginx -y

Create Nginx configuration:

sudo nano /etc/nginx/sites-available/convex

Paste:

# Backend API
server {
    listen 80;
    server_name api.yourdomain.com;

    location / {
        proxy_pass http://localhost:3210;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Dashboard
server {
    listen 80;
    server_name dashboard.yourdomain.com;

    location / {
        proxy_pass http://localhost:6791;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enable site and get SSL:

# Enable site
sudo ln -s /etc/nginx/sites-available/convex /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

# Get SSL certificates
sudo certbot --nginx -d api.yourdomain.com -d dashboard.yourdomain.com

Update your .env file with production URLs:

CONVEX_CLOUD_ORIGIN=https://api.yourdomain.com
CONVEX_SITE_ORIGIN=https://backend.yourdomain.com
NEXT_PUBLIC_DEPLOYMENT_URL=https://api.yourdomain.com

Restart services:

docker-compose down
docker-compose up -d

Step 7: Generate Admin Key

# Access backend container
docker-compose exec backend /bin/sh

# Navigate to convex directory and generate admin key
cd convex
./generate_admin_key.sh

# Exit container
exit

Save the admin key securely - you’ll need it for your projects.

Using Convex in Your Projects

With your self-hosted Convex backend running, here’s how to connect your applications.

Configure Your Project Environment

Add these two environment variables to your application’s .env.local file (don’t commit this to git):

CONVEX_SELF_HOSTED_URL=https://backend.convex.yourdomain.com
CONVEX_SELF_HOSTED_ADMIN_KEY=convex-self-hosted|015dfa7184876e556124a4ad005ffae7ace340d3d230a5c19106d94c1cdbb183bccf3dee06

Replace the URL with your actual backend URL (custom domain or traefik.me URL) and the admin key with the one you generated earlier. The Convex CLI will use your self-hosted backend when these variables are set.

Advanced Configuration

Using External PostgreSQL (Neon, Supabase, AWS RDS)

For managed PostgreSQL:

  1. Create a database named convex_self_hosted
  2. Get the connection string (without database name and query params)
  3. Update environment:
POSTGRES_URL=postgresql://user:password@host.region.provider.com:5432
DO_NOT_REQUIRE_SSL=false

Example for Neon:

POSTGRES_URL=postgresql://user:password@ep-example-123.us-east-2.aws.neon.tech:5432

Same Region Required

Put your Convex backend in the same region as your PostgreSQL database. Latency between them will slow down query performance.

Configuring S3 Storage

For production file storage, configure S3:

services:
  backend:
    environment:
      # ... other vars ...
      - AWS_REGION=us-east-1
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
      - S3_STORAGE_EXPORTS_BUCKET=convex-snapshot-exports
      - S3_STORAGE_SNAPSHOT_IMPORTS_BUCKET=convex-snapshot-imports
      - S3_STORAGE_MODULES_BUCKET=convex-modules
      - S3_STORAGE_FILES_BUCKET=convex-user-files
      - S3_STORAGE_SEARCH_BUCKET=convex-search-indexes

Create the buckets in AWS S3 or use S3-compatible storage like Cloudflare R2:

S3_ENDPOINT_URL=https://<account-id>.r2.cloudflarestorage.com
AWS_ACCESS_KEY_ID=<your-r2-access-key>
AWS_SECRET_ACCESS_KEY=<your-r2-secret-key>

Custom Domains for HTTP Actions

To serve HTTP actions from a custom domain:

  1. Set up DNS for api.yourdomain.com
  2. Configure in your environment:
CONVEX_SITE_ORIGIN=https://api.yourdomain.com
  1. In your frontend, override the environment variable during build:
CONVEX_SITE_URL=https://api.yourdomain.com

Migration Between Storage Providers

If switching from SQLite to PostgreSQL or changing S3 configuration:

# Export data from old backend
npx convex export --path backup.zip

# Deploy new backend with different storage
# ...

# Import data to new backend
npx convex import --replace-all backup.zip

Maintenance and Backups

Regular Backups

For Dokploy deployments, you can configure automated backups through Dokploy’s interface or follow our Dokploy Backups Guide.

For Docker Compose with PostgreSQL:

# Manual backup
docker-compose exec postgres pg_dump -U postgres convex_self_hosted > backup-$(date +%Y%m%d).sql

# Restore backup
cat backup-20241124.sql | docker-compose exec -T postgres psql -U postgres convex_self_hosted

Automated backup script (backup.sh):

#!/bin/bash
BACKUP_DIR="/backups/convex"
DATE=$(date +%Y%m%d-%H%M)
mkdir -p $BACKUP_DIR

docker-compose exec -T postgres pg_dump -U postgres convex_self_hosted | gzip > $BACKUP_DIR/convex-$DATE.sql.gz

# Keep only last 7 days
find $BACKUP_DIR -name "convex-*.sql.gz" -mtime +7 -delete

Make it executable and add to crontab:

chmod +x backup.sh
crontab -e
# Add: 0 2 * * * /path/to/backup.sh

Updating Convex

With Dokploy:

  1. Go to your service
  2. Click “Redeploy”
  3. Dokploy pulls latest image and restarts

With Docker Compose:

cd ~/convex-backend
docker-compose pull
docker-compose up -d

Version Pinning

For production, consider pinning to a specific version instead of :latest:

image: ghcr.io/get-convex/convex-backend:v0.1.0

Check releases for versions.

Security Best Practices

  • Secure Instance Secret: Use a strong, random INSTANCE_SECRET and never expose it
  • HTTPS Only: Always use SSL/TLS for production deployments
  • Strong Passwords: Use complex passwords for PostgreSQL and admin accounts
  • Firewall Configuration: Only expose necessary ports (80, 443)
  • Regular Updates: Keep Convex, Docker, and system packages updated
  • Backup Encryption: Encrypt database backups at rest
  • Environment Variables: Never commit .env files to version control
  • Admin Key Rotation: Regenerate admin keys periodically
  • Network Isolation: Use Docker networks to isolate services
  • Monitor Logs: Set up log monitoring for suspicious activity

Conclusion

Self-hosting Convex gives you control over your real-time backend infrastructure while keeping Convex’s developer experience. Whether you use Dokploy or Docker Compose, you can have a production-ready Convex deployment in minutes.

Convex handles real-time queries, serverless functions, file storage, and search. Self-hosting adds infrastructure control to those features.

For smaller applications, SQLite works well with minimal resources. When you need to scale, PostgreSQL handles production workloads more reliably.

Next Steps

Learn More About Convex

Questions about self-hosting Convex? Leave a comment below.

Frequently Asked Questions

Should I use SQLite or PostgreSQL?

SQLite works well for:

  • Development and testing
  • Small to medium applications (< 10k requests/day)
  • Single-server deployments
  • Prototypes

PostgreSQL is better when:

  • You have production workloads with high traffic
  • You need high availability
  • You want database replication
  • You use managed database services (Neon, Supabase, AWS RDS)

Start with SQLite and move to PostgreSQL when you need more reliability.

Can I migrate from cloud-hosted Convex to self-hosted?

Yes! The process is straightforward:

  1. Export data from cloud: npx convex export --prod
  2. Set up self-hosted backend
  3. Import data: npx convex import --replace-all
  4. Update environment variables in your frontend
  5. Redeploy functions: npx convex deploy

Your application code doesn’t need to change - just update the backend URL.

How much does self-hosting Convex cost?

Monthly costs (example):

  • VPS with 4GB RAM (Hetzner): $8/month
  • PostgreSQL on Neon: Free tier or $19/month for Pro
  • Domain: $1/month
  • S3 storage (optional): ~$1-5/month

Total: $10-30/month

Self-hosting can save money compared to cloud Convex for high-traffic apps.

Can I use self-hosted Convex in production?

Absolutely! Many companies run self-hosted Convex in production. Make sure to:

  • Use PostgreSQL instead of SQLite
  • Set up automated backups
  • Configure proper monitoring
  • Use SSL/TLS
  • Run in a reliable hosting environment
  • Keep the backend updated

Follow the production deployment guidelines in this article for a reliable setup.

Does self-hosted Convex support all cloud features?

Self-hosted Convex supports all free-tier features:

  • Real-time queries and mutations
  • Serverless functions
  • File storage
  • Scheduled functions
  • Full-text search
  • Vector search
  • HTTP actions

The main difference is that you handle infrastructure management: backups, scaling, and maintenance.

How do I scale self-hosted Convex?

For vertical scaling:

  • Increase VPS resources (CPU/RAM)
  • Upgrade to a larger PostgreSQL instance
  • Use S3 for file storage

For horizontal scaling:

  • Use a managed PostgreSQL with read replicas
  • Put Convex behind a load balancer
  • Configure multiple backend instances

Most applications won’t need horizontal scaling. Vertical scaling handles substantial traffic.

Can I use Convex Auth with self-hosted?

Convex Auth works with self-hosted deployments. Follow the manual setup instructions in the Convex Auth documentation. The CLI’s automatic setup doesn’t support self-hosted yet.

What happens if my backend goes down?

With proper setup:

  • Docker restarts crashed containers
  • PostgreSQL data persists in volumes
  • Recent operations may need to be retried by clients
  • Real-time subscriptions reconnect automatically

For high availability:

  • Use a monitoring service (UptimeRobot, Pingdom)
  • Set up PostgreSQL replication
  • Consider running multiple backend instances
  • Set up regular automated backups