Docker AIO (All-in-One) Commercial Edition
Not for production
Use Docker AIO only to evaluate Plane or run a proof of concept. For production instances, deploy with Docker Compose or Kubernetes.
AIO runs every Plane service in one container, so you can't scale, restart, or upgrade services independently, and a problem in one service affects the whole instance.
The Plane Commercial All-in-One (AIO) Docker image packages all Plane services into a single container. It's the fastest way to try Plane.
What's included
Your single AIO container includes all these services running together:
- Web App - The main Plane web interface you'll use
- Space - Public project spaces for external collaboration
- Admin - Administrative interface
- API Server - Backend API
- Live Server - Real-time collaboration features
- Silo - Integration services
- Monitor - Feature flags and payments
- Email Server - SMTP server for notifications
- Proxy (Port 80, 20025, 20465, 20587) - Caddy reverse proxy
- Worker and Beat Worker - Background task processing
Port Mapping
The following ports are exposed:
80: Main web interface (HTTP)443: HTTPS (if SSL configured)20025: SMTP port 2520465: SMTP port 465 (SSL/TLS)20587: SMTP port 587 (STARTTLS)
Prerequisites
- Docker
- Set up these external services:
- PostgreSQL
For data storage - Redis
For caching and session management - RabbitMQ For message queuing
- S3-compatible storage
For file uploads (AWS S3 or MinIO)
- PostgreSQL
Install Plane
Download the image with:
bashdocker pull makeplane/plane-aio-commercial:stableRun the following command to deploy the Plane AIO container. Make sure to replace all placeholder values (e.g.,
your-domain.com,user:pass) with your actual configuration.WARNING
All environment variables are required for the container to function correctly.
bashdocker run --name plane-aio --rm -it \ -p 80:80 \ -p 20025:20025 \ -p 20465:20465 \ -p 20587:20587 \ -e DOMAIN_NAME=your-domain.com \ -e DATABASE_URL=postgresql://user:pass@host:port/database \ -e REDIS_URL=redis://host:port \ -e AMQP_URL=amqp://user:pass@host:port/vhost \ -e AWS_REGION=us-east-1 \ -e AWS_ACCESS_KEY_ID=your-access-key \ -e AWS_SECRET_ACCESS_KEY=your-secret-key \ -e AWS_S3_BUCKET_NAME=your-bucket \ makeplane/plane-aio-commercial:stableIf you're running on an IP address, use this example:
bashMYIP=192.168.68.169 docker run --name myaio --rm -it \ -p 80:80 \ -p 20025:20025 \ -p 20465:20465 \ -p 20587:20587 \ -e DOMAIN_NAME=${MYIP} \ -e DATABASE_URL=postgresql://plane:plane@${MYIP}:15432/plane \ -e REDIS_URL=redis://${MYIP}:16379 \ -e AMQP_URL=amqp://plane:plane@${MYIP}:15673/plane \ -e AWS_REGION=us-east-1 \ -e AWS_ACCESS_KEY_ID=<YOUR_AWS_ACCESS_KEY_ID> \ -e AWS_SECRET_ACCESS_KEY=<YOUR_AWS_SECRET_ACCESS_KEY> \ -e AWS_S3_BUCKET_NAME=plane-app \ -e AWS_S3_ENDPOINT_URL=http://${MYIP}:19000 \ -e FILE_SIZE_LIMIT=10485760 \ makeplane/plane-aio-commercial:stableYou can also keep your values in a file and pass it with
--env-file. With the currentstableimage, values you pass with-eor--env-filealways take precedence over the defaults built into the image. Older images could replace some of them, such as the Slack credentials, with empty defaults. The image still sets its own version and the addresses its internal services use to reach each other.Once it's running, you can access the Plane application on the domain you provided during the deployment.
If you've purchased a paid plan, activate your license key to unlock premium features.
Volume mounts
Recommended persistent volumes
-v /path/to/logs:/app/logs \
-v /path/to/data:/app/dataKeep /app/data mounted. Your license is bound to the instance's MACHINE_SIGNATURE. With the current stable image, if you don't set MACHINE_SIGNATURE, the container generates one and stores it in /app/data, so it reuses the same signature when you recreate the container. Without the volume, a recreated container gets a new signature. To avoid depending on the volume, set MACHINE_SIGNATURE yourself. See Security and secrets.
Workspace license DB
-v /path/to/monitordb:/app/monitorSSL certificate support
For HTTPS support, mount certificates:
-v /path/to/certs:/app/email/tlsEnvironment variables (optional)
Network and Protocol
SITE_ADDRESS: Server bind address (default::80)APP_PROTOCOL: Protocol to use (httporhttps, default:http)
Email configuration
INTAKE_EMAIL_DOMAIN: Domain for intake emails (default:intake.<DOMAIN_NAME>)LISTEN_SMTP_PORT_25: SMTP port 25 mapping (default:20025)LISTEN_SMTP_PORT_465: SMTP port 465 mapping (default:20465)LISTEN_SMTP_PORT_587: SMTP port 587 mapping (default:20587)SMTP_DOMAIN: SMTP server domain (default:0.0.0.0)TLS_CERT_PATH: Path to TLS certificate file (optional)TLS_PRIV_KEY_PATH: Path to TLS private key file (optional)
Licensing
PRIME_HOST: License server URL (default:https://prime.plane.so). Set it only for an air-gapped or custom license server.
Security and secrets
MACHINE_SIGNATURE: Unique machine identifier that your license is bound to. Use at least 24 characters, made of letters, digits,-, or_, such as the output ofuuidgen. If you don't set it, the container generates one and stores it in/app/data. A value that doesn't meet these rules is ignored with a warning in the container log.SECRET_KEY: Django secret key (default provided)SILO_HMAC_SECRET_KEY: Silo HMAC secret (default provided)AES_SECRET_KEY: AES encryption key for stored integration credentials (default provided)LIVE_SERVER_SECRET_KEY: Live server secret (default provided)
The container logs a warning at startup when any of SECRET_KEY, SILO_HMAC_SECRET_KEY, AES_SECRET_KEY, or LIVE_SERVER_SECRET_KEY uses the default value shipped with the image. Set unique values for all four when you create a new instance. For example, generate each one with openssl rand -hex 32.
On an existing instance:
- You can change
SECRET_KEY. All users are signed out. - Don't change
AES_SECRET_KEY. It encrypts the integration credentials stored in the database, and a new value makes them unreadable.
Webhooks
WEBHOOK_ALLOWED_HOSTS: Comma-separated hostnames, not URLs, that webhooks may target even when they resolve to a private IP address. When it's not set andDOMAIN_NAMEis a domain name, it defaults toDOMAIN_NAME, so Plane's own integrations, such as Slack, can receive webhook deliveries on your instance. There's no default whenDOMAIN_NAMEislocalhostor an IP address, because listed hosts bypass the private-network check.
File handling
FILE_SIZE_LIMIT: Maximum file upload size in bytes (default:5242880= 5MB)
Integration callbacks
INTEGRATION_CALLBACK_BASE_URL: Base URL that integrations such as Slack, GitHub, and GitLab use for OAuth callbacks and webhooks, with protocol, for examplehttps://plane.example.com(default:APP_PROTOCOL://DOMAIN_NAME). Set it only when integrations must reach Plane at a different address than your users do.
API configuration
API_KEY_RATE_LIMIT: API key rate limit (default:60/minute)
Third-party integrations
GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET: GitHub integrationGITHUB_APP_NAME,GITHUB_APP_ID,GITHUB_PRIVATE_KEY: GitHub App integration. SetGITHUB_APP_NAMEto the app's slug, the last part of its public URLhttps://github.com/apps/<slug>, not its display name. See Configure GitHub.SLACK_CLIENT_ID,SLACK_CLIENT_SECRET: Slack integrationGITLAB_CLIENT_ID,GITLAB_CLIENT_SECRET: GitLab integration
Build the image
To build the AIO image yourself:
cd deploy/aio/commercial
./build.sh --release=v1.11.1Available build options:
--release: Plane version to build (required)--image-name: Custom image name (default:plane-aio-commercial)
Troubleshoot
The container will validate required environment variables on startup and display helpful error messages if any are missing.
Logs
All service logs are available in /app/logs/:
- Access logs:
/app/logs/access/ - Error logs:
/app/logs/error/
Health checks
The container runs multiple services managed by Supervisor. Check service status:
docker exec -it <container-name> supervisorctl statusMove to production
Docker AIO isn't supported for production. When you're ready to run Plane for your team, deploy with Docker Compose or Kubernetes.

