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 -dAfter starting, access:
- Web Dashboard: http://localhost:3002
- Documentation: http://localhost:3005
- API: http://localhost:4002
- Handler (serves deployments): http://localhost:4001
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:latestNote: 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 -dOption 2: Separate Services (Recommended for Production)
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 -dServices 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:
- Go to GitHub Settings → Developer settings → OAuth Apps
- Click New OAuth App
- Fill in:
- Application name: Ploy
- Homepage URL:
http://localhost:3002(or your domain) - Authorization callback URL:
http://localhost:3002/api/auth/callback/github
- Click Register application
- Copy the Client ID and generate a Client Secret
2. GitHub App (for Repository Access)
Create a GitHub App for repository access and webhooks:
- Go to GitHub Settings → Developer settings → GitHub Apps
- Click New GitHub App
- 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
- Set permissions:
- Repository permissions:
- Contents: Read
- Metadata: Read
- Webhooks: Read & Write
- Repository permissions:
- Subscribe to events:
- Push
- Repository
- Click Create GitHub App
- Generate and download a private key
- 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/fs4. Generate Secrets
Generate secure random strings for secrets:
# Generate AUTH_SECRET
openssl rand -hex 32
# Generate GITHUB_WEBHOOK_SECRET
openssl rand -hex 32Database Storage Modes
Ploy supports two storage modes for project databases:
basicis the default. It keeps each project database as a local SQLite file insideDB_STORAGE_DIR.turso-cloudis 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-storageUse 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_tokenOptional:
TURSO_API_URL=https://api.turso.tech
TURSO_PITR_RETENTION_DAYS=30Use 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-storageThe 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
- Point your domain to your server's IP
- 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/githubSSL/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 runThe 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=httpsIt 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: 512MMonitoring
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 statsManagement 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 -vDocker 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 downDatabase 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 ployTroubleshooting
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 :4002Database connection errors:
# Check PostgreSQL is running
docker compose ps postgres
# Verify DATABASE_URL in .env
# Ensure POSTGRES_PASSWORD matches in DATABASE_URLGitHub webhook not working:
- Check webhook URL is publicly accessible
- Verify GITHUB_WEBHOOK_SECRET matches GitHub App
- Check API logs:
docker compose logs -f api - 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/fsDebug Mode
Enable debug logging:
# Add to .env
LOG_LEVEL=debug
# Restart services
docker compose restartUpdating 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 -fBuild 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 -dPerformance 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=100Redis
Configure Redis memory limits:
# Add to docker-compose.yml redis service
command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lruWorker Concurrency
Adjust worker concurrency:
# Add to .env
WORKER_CONCURRENCY=5Security Best Practices
- Use strong passwords for database and secrets
- Enable HTTPS in production with valid SSL certificates
- Keep secrets secure - never commit
.envfiles - Regular backups of database and deployment files
- Update regularly to get security patches
- Firewall rules - only expose necessary ports
- Limit GitHub App permissions to minimum required
Next Steps
Once Ploy is running:
- Sign in at http://localhost:3002 (or your domain)
- Create an organization and project
- Connect a repository and deploy your first app
- Configure custom domains for production deployments
- Explore the API at http://localhost:4002/docs
Support
- Documentation: https://docs.meetploy.com
- GitHub Issues: https://github.com/meetploy/ploy/issues
- Community: Join our Discord or discussions
Happy self-hosting! ✨
How is this guide?
Last updated on