Ploy
Ploy

Self Host Ploy

Deploy Ploy on your own infrastructure using Docker.

Self Host Ploy

Ploy is a self-hostable serverless deployment platform and Vercel alternative. This guide will help you deploy Ploy on your own infrastructure, giving you complete control over your deployments, code, and data.

Prerequisites

  • Docker (latest version) and Docker Compose
  • GitHub OAuth App (for authentication)
  • Domain (optional, but recommended for production)
  • 2GB+ RAM (4GB+ recommended for production)
  • 10GB+ disk space (more for storing deployments)

Quick Start

The fastest way to get started is with Docker Compose:

# Clone the repository
git clone https://github.com/meetploy/ploy.git
cd ploy

# Copy environment variables
cp .env.example .env

# Edit .env with your configuration (see below)
nano .env

# Start all services
docker compose up -d

After starting, access:

Option 1: Unified Docker Image (Simplest)

This option uses a single Docker container with all services bundled together. Perfect for testing or small deployments.

# Run the unified container
docker run -d \
  --name ploy \
  --restart unless-stopped \
  -p 3002:3002 \
  -p 3005:3005 \
  -p 4001:4001 \
  -p 4002:4002 \
  -v ~/ploy_data:/var/lib/postgresql/data \
  -v ~/ploy_fs:/app/fs \
  -e AUTH_SECRET=your-secret-key-here \
  -e GITHUB_CLIENT_ID=your-github-client-id \
  -e GITHUB_CLIENT_SECRET=your-github-client-secret \
  -e GITHUB_WEBHOOK_SECRET=your-webhook-secret \
  ghcr.io/meetploy/ploy-unified:latest

Note: Replace latest with a specific version from releases.

Using Docker Compose (Unified)

# Download the compose file
curl -O https://raw.githubusercontent.com/meetploy/ploy/main/infra/docker-compose.unified.yml

# Copy and configure environment
curl -O https://raw.githubusercontent.com/meetploy/ploy/main/.env.example
cp .env.example .env
# Edit .env with your configuration

# Start the service
docker compose -f docker-compose.unified.yml up -d

This option runs each service in its own container, providing better scalability and resource management.

# Clone the repository
git clone https://github.com/meetploy/ploy.git
cd ploy

# Configure environment
cp .env.example .env
# Edit .env with your configuration

# Start all services
docker compose -f infra/docker-compose.split.yml up -d

Services in Split Mode

The split deployment includes:

  • postgres - PostgreSQL database
  • redis - Redis cache and session store
  • api - REST API server
  • ui - Web dashboard (Next.js)
  • builder - Background job processor
  • handler - Deployment handler (serves deployed apps)
  • docs - Documentation site

Private service network

Only the API, UI, and deployment handler should accept traffic from outside the service network. Keep the internal API, database service, broker, sandbox control plane, and handler internal-delivery port private. The production and local Compose files do this by omitting host port mappings for those services. Direct development runs bind the internal API, database service, and broker to 127.0.0.1 by default. Set HOST=0.0.0.0 only when the process is already protected by a private container or host network.

To inspect a private service, run the probe inside its container instead of publishing its port. For example:

docker compose -f infra/docker-compose.split.local.yml exec internal \
  wget --spider http://localhost:80/

For Kubernetes or another orchestrator, enforce the same boundary with firewall rules or NetworkPolicies: only platform services may reach the private service ports, and only the API, broker, and cron worker may reach the handler's internal delivery port (8081).

Required Configuration

1. GitHub OAuth App

Create a GitHub OAuth App for authentication:

  1. Go to GitHub Settings → Developer settings → OAuth Apps
  2. Click New OAuth App
  3. Fill in:
    • Application name: Ploy
    • Homepage URL: http://localhost:3002 (or your domain)
    • Authorization callback URL: http://localhost:3002/api/auth/callback/github
  4. Click Register application
  5. Copy the Client ID and generate a Client Secret

2. GitHub App (for Repository Access)

Create a GitHub App for repository access and webhooks:

  1. Go to GitHub Settings → Developer settings → GitHub Apps
  2. Click New GitHub App
  3. Fill in:
    • GitHub App name: Ploy Deployments
    • Homepage URL: http://localhost:3002
    • Webhook URL: http://localhost:4002/webhooks/github
    • Webhook secret: Generate a random string
  4. Set permissions:
    • Repository permissions:
      • Contents: Read
      • Metadata: Read
      • Webhooks: Read & Write
  5. Subscribe to events:
    • Push
    • Repository
  6. Click Create GitHub App
  7. Generate and download a private key
  8. Note the App ID

3. Environment Variables

Edit your .env file with the following required variables:

# Database
POSTGRES_PASSWORD=your_secure_password_here
DATABASE_URL=postgresql://postgres:your_secure_password_here@postgres:5432/ploy

# Redis
REDIS_URL=redis://redis:6379

# Authentication
AUTH_SECRET=your-secret-key-here
BETTER_AUTH_URL=http://localhost:3002

# GitHub OAuth (for user authentication)
GITHUB_CLIENT_ID=your-github-oauth-client-id
GITHUB_CLIENT_SECRET=your-github-oauth-client-secret

# GitHub App (for repository access)
GITHUB_APP_ID=your-github-app-id
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
GITHUB_WEBHOOK_SECRET=your-webhook-secret

# API URLs
NEXT_PUBLIC_API_URL=http://localhost:4002
API_HANDLER_URL=http://handler:4001

# Deployment Storage
FS_PATH=/app/fs

4. Generate Secrets

Generate secure random strings for secrets:

# Generate AUTH_SECRET
openssl rand -hex 32

# Generate GITHUB_WEBHOOK_SECRET
openssl rand -hex 32

Database Storage Modes

Ploy supports two storage modes for project databases:

  • basic is the default. It keeps each project database as a local SQLite file inside DB_STORAGE_DIR.
  • turso-cloud is opt-in. It provisions databases in Turso Cloud and uses Turso-managed replication and point-in-time restore.

Basic Mode

basic matches the simplest self-hosted setup and is what the bundled compose files use by default.

Required environment variables:

DB_STORAGE_MODE=basic
DB_STORAGE_DIR=/data/db-storage

Use this mode when:

  • you are running Ploy on a single machine
  • you want the fewest moving pieces
  • you are okay with local SQLite files and handling their durability yourself

Turso Cloud Mode

Set DB_STORAGE_MODE=turso-cloud to place worker databases in Turso Cloud. When enabled, Ploy creates Turso databases on demand and routes all worker database traffic there.

Required environment variables:

DB_STORAGE_MODE=turso-cloud
TURSO_ORGANIZATION=your-turso-organization
TURSO_GROUP_NAME=default
TURSO_PRIMARY_LOCATION=ord
TURSO_DATABASE_PREFIX=ploy
TURSO_API_TOKEN=your_turso_platform_api_token
TURSO_GROUP_AUTH_TOKEN=your_turso_group_auth_token

Optional:

TURSO_API_URL=https://api.turso.tech
TURSO_PITR_RETENTION_DAYS=30

Use this mode when:

  • you want managed replicas and failover from Turso
  • you want Turso-backed point-in-time recovery
  • you do not want to run your own libSQL cluster

Compose Defaults

The compose files in this repository default to:

DB_STORAGE_MODE=basic
DB_STORAGE_DIR=/data/db-storage

The older libSQL containers remain available only behind an optional legacy-libsql compose profile and are no longer started by default.

Production Configuration

For production deployments, consider these additional settings:

Domain Setup

  1. Point your domain to your server's IP
  2. Update environment variables:
# Frontend URL
BETTER_AUTH_URL=https://ploy.yourdomain.com
NEXT_PUBLIC_API_URL=https://api.yourdomain.com

# Update GitHub OAuth callback
# In GitHub OAuth App settings, set callback to:
# https://ploy.yourdomain.com/api/auth/callback/github

# Update GitHub App webhook URL
# In GitHub App settings, set webhook to:
# https://api.yourdomain.com/webhooks/github

SSL/TLS Certificates

Use a reverse proxy like Nginx or Caddy for SSL:

Caddy Example

Create a Caddyfile:

ploy.yourdomain.com {
    reverse_proxy localhost:3002
}

api.yourdomain.com {
    reverse_proxy localhost:4002
}

*.ploy-deployments.yourdomain.com {
    reverse_proxy localhost:4001
}

Run Caddy:

caddy run

The reverse proxy terminates TLS and forwards plain HTTP to the handler, so the handler cannot tell how the client connected. Tell it with PUBLIC_PROTOCOL, which is the protocol worker code sees in request.url:

# Deployments are served over https by the reverse proxy
PUBLIC_PROTOCOL=https

It defaults to https when NODE_ENV=production and http otherwise. Set PUBLIC_PROTOCOL=http if your deployments are served without TLS.

Resource Limits

Add resource limits to your docker-compose.yml:

services:
  api:
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 1G
        reservations:
          cpus: "0.5"
          memory: 512M

Monitoring

Enable logging and monitoring:

# View logs for all services
docker compose logs -f

# View logs for specific service
docker compose logs -f api

# View resource usage
docker stats

Management Commands

Docker Compose (Unified)

# Start services
docker compose -f docker-compose.unified.yml up -d

# View logs
docker compose -f docker-compose.unified.yml logs -f

# Restart services
docker compose -f docker-compose.unified.yml restart

# Stop services
docker compose -f docker-compose.unified.yml down

# Stop and remove volumes
docker compose -f docker-compose.unified.yml down -v

Docker Compose (Split)

# Start all services
docker compose -f infra/docker-compose.split.yml up -d

# Start specific service
docker compose -f infra/docker-compose.split.yml up -d api

# View logs
docker compose -f infra/docker-compose.split.yml logs -f

# Restart services
docker compose -f infra/docker-compose.split.yml restart

# Stop services
docker compose -f infra/docker-compose.split.yml down

Database Operations

# Backup database
docker exec ploy-postgres pg_dump -U postgres ploy > backup.sql

# Restore database
docker exec -i ploy-postgres psql -U postgres ploy < backup.sql

# Access database shell
docker exec -it ploy-postgres psql -U postgres ploy

Troubleshooting

Common Issues

Services won't start:

# Check logs
docker compose logs -f

# Verify environment variables
docker compose config

# Ensure ports are not in use
lsof -i :3002
lsof -i :4002

Database connection errors:

# Check PostgreSQL is running
docker compose ps postgres

# Verify DATABASE_URL in .env
# Ensure POSTGRES_PASSWORD matches in DATABASE_URL

GitHub webhook not working:

  1. Check webhook URL is publicly accessible
  2. Verify GITHUB_WEBHOOK_SECRET matches GitHub App
  3. Check API logs: docker compose logs -f api
  4. Test webhook delivery in GitHub App settings

Deployments failing:

# Check worker logs
docker compose logs -f builder

# Check handler logs
docker compose logs -f handler

# Verify FS_PATH volume is writable
docker exec ploy-builder ls -la /app/fs

Debug Mode

Enable debug logging:

# Add to .env
LOG_LEVEL=debug

# Restart services
docker compose restart

Updating Ploy

To update to the latest version:

# Pull latest changes
git pull origin main

# Pull latest images
docker compose pull

# Restart services
docker compose down
docker compose up -d

# Check logs for any issues
docker compose logs -f

Build from Source

To build Ploy from source:

# Clone repository
git clone https://github.com/meetploy/ploy.git
cd ploy

# Install dependencies
pnpm install

# Build all packages
pnpm build

# Start services with local build
docker compose -f infra/docker-compose.local.yml up -d

Performance Tuning

PostgreSQL

Optimize PostgreSQL for your workload:

# Add to docker-compose.yml postgres service
environment:
  - POSTGRES_SHARED_BUFFERS=256MB
  - POSTGRES_EFFECTIVE_CACHE_SIZE=1GB
  - POSTGRES_MAX_CONNECTIONS=100

Redis

Configure Redis memory limits:

# Add to docker-compose.yml redis service
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru

Worker Concurrency

Adjust worker concurrency:

# Add to .env
WORKER_CONCURRENCY=5

Security Best Practices

  1. Use strong passwords for database and secrets
  2. Enable HTTPS in production with valid SSL certificates
  3. Keep secrets secure - never commit .env files
  4. Regular backups of database and deployment files
  5. Update regularly to get security patches
  6. Firewall rules - only expose necessary ports
  7. Limit GitHub App permissions to minimum required

Next Steps

Once Ploy is running:

  1. Sign in at http://localhost:3002 (or your domain)
  2. Create an organization and project
  3. Connect a repository and deploy your first app
  4. Configure custom domains for production deployments
  5. Explore the API at http://localhost:4002/docs

Support

Happy self-hosting! ✨

How is this guide?

Last updated on