Deployment guide

Deploy Plane on a VPS: guide and post-2.3.7 regressions

Deploy on a VPS Cloud →

Automation12 min read

Deploy Plane on a VPS: guide and post-2.3.7 regressions

Linear or Jira bills grow with every new seat, and storing your project tickets with a third-party vendor is not always acceptable. Plane is an AGPL-3.0 alternative with over 54,000 GitHub stars, a maintained Docker AIO image — `makeplane/plane-aio-community:stable` — and a single container that groups all internal services under supervisord. This guide shows you how to deploy it on a Linux VPS, secure it behind an HTTPS reverse proxy, and open it to your team.

Why self-host Plane instead of paying per seat

The most common objection to the AIO image is that 'all-in-one supervisord' hides several internal services, making debugging harder when something goes wrong. In practice, the image groups five components — Django API, Celery worker, PostgreSQL, Redis and a file server — into a single supervisord process, which drastically simplifies startup. You do not need to compose a multi-container stack, synchronise migrations, or manage five separate Docker logs. When an error occurs, docker logs <container> and docker exec <container> supervisorctl status cover the vast majority of cases. Plane's official blog reports over 100,000 Docker deployments and over 44,000 Kubernetes deployments, which gives a measure of the image's operational maturity. The economic model is straightforward: you pay for the VPS, not the seat.

What you gain by hosting Plane on your own VPS

  • Fixed cost, independent of team size — a single VPS handles 5 to 50 users; pricing does not vary with seat count.
  • Data under your control — tickets, comments, files and team members stay in your own PostgreSQL database, on your own disk.
  • AGPL-3.0 licence — commercial use and self-hosting authorised without royalties; source code is auditable.
  • Issues, cycles, modules, pages and inbox — Plane covers task tracking, sprints, functional groupings, lightweight documentation and incoming triage, with no separate module.
  • Stable, maintained imagemakeplane/plane-aio-community:stable is updated by the publisher and tested as a coherent unit before each release.
  • Controlled updates — you pull the new image when you choose to; no vendor can modify your production environment without your consent.
  • Integration with your toolchain — Plane exposes a documented REST API, usable to synchronise issues from a CI pipeline or from GitHub.
  • Simple incident resolution — one container, one log, one restart point; no multi-service stack to orchestrate manually.

Minimum requirements for a stable deployment

The AIO image bundles several services in a single container: plan for 2 vCPU and 4 GB of RAM as a minimum for team use. Below that threshold, the Celery worker and PostgreSQL share too little memory and indexing queries take several seconds. For teams of more than ten people or heavy use of pages and cycles, move to 8 GB. You need Docker installed on the host (version 20 or higher), a domain or subdomain pointing to your VPS, ports 80 and 443 open in your firewall, and around 10 GB of disk space for data volumes and Docker images. A TLS certificate is mandatory: Plane sets session cookies with Secure, making them unusable over plain HTTP.

Deploy Plane AIO in eight steps

01

Prepare the host and install Docker

On a freshly installed Debian or Ubuntu VPS, update packages, then install Docker via the official script or Docker's APT repositories:

curl -fsSL https://get.docker.com | sh
systemctl enable --now docker

Verify that Docker is running: docker version.

02

Create the working directory and environment file

Create a dedicated folder and prepare the minimal .env file:

mkdir -p /opt/plane && cd /opt/plane

Then create /opt/plane/.env with the required variables:

SECRET_KEY=$(openssl rand -hex 32)
WEB_URL=https://plane.your-domain.com
DATABASE_URL=postgresql://plane:[email protected]:5432/plane

SECRET_KEY must be a long random string; WEB_URL is the final public URL of your instance — this is the most critical value. If it is wrong, post-login redirects and asset loading will fail.

03

Start the AIO container

Launch Plane with the following command, specifying the path to your .env file and mounting a volume for persistent data:

docker run -d \
  --name plane \
  --restart unless-stopped \
  --env-file /opt/plane/.env \
  -v plane-data:/app/plane-data \
  -p 127.0.0.1:8080:8080 \
  makeplane/plane-aio-community:stable

Port 8080 is exposed on loopback only: only the local reverse proxy can reach it. On first start, supervisord runs Django migrations; the interface is not available for one to two minutes.

04

Check internal service status

Before configuring the reverse proxy, verify that all sub-processes are active:

docker exec plane supervisorctl status

You should see the api, worker, beat, web and nginx services in RUNNING state. If any is in FATAL, read the logs with docker logs plane to identify the error.

05

Obtain a TLS certificate with Certbot

Install Certbot and the Nginx plugin, then request a certificate for your subdomain:

apt install -y certbot python3-certbot-nginx
certbot certonly --nginx -d plane.your-domain.com

Certbot will place the certificate files in /etc/letsencrypt/live/plane.your-domain.com/.

06

Configure Nginx as HTTPS reverse proxy

Create /etc/nginx/sites-available/plane.conf:

server {
    listen 80;
    server_name plane.your-domain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name plane.your-domain.com;
    ssl_certificate /etc/letsencrypt/live/plane.your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/plane.your-domain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        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 the site and reload Nginx:

ln -s /etc/nginx/sites-available/plane.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
07

Create the first administrator account

Open https://plane.your-domain.com in a browser. Plane shows a sign-up page on first access. Create an administrator account, then from the admin panel — accessible at /god-mode/ — enable email sign-ups or configure a domain allowlist to restrict access to your organisation.

08

Invite the team and create the first project

In Plane, a project groups issues, cycles (sprints), modules (features) and pages (lightweight wiki). Create a project from the interface, then invite collaborators by email from the project settings. Invitations are sent by the mail server configured in your .env via EMAIL_HOST and EMAIL_PORT.

Post-installation configuration: key environment variables

The most important variables after the initial startup are:

WEB_URL — the full public URL of the instance, without a trailing slash. An incorrect URL causes broken post-login redirects and assets that fail to load (images, CSS, JS). This is the most common error on first startup.

SECRET_KEY — secret string for signing Django sessions. Do not change it after the first startup without invalidating all active sessions.

EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD — required for invitations and notifications. Without these variables, email invitations are not sent.

ENABLE_SIGNUP1 to allow open sign-ups, 0 to disable them (only the admin can create accounts).

After any change to the .env file, restart the container: docker restart plane.

To update Plane, pull the new image then recreate the container while keeping the data volume:

docker pull makeplane/plane-aio-community:stable
docker stop plane && docker rm plane

Then re-run the docker run command from step 3 with the same arguments. Database migrations are applied automatically on startup. If an update breaks the environment, roll back to the previous version by replacing stable with the exact tag of the previous image — docker images lists locally available images.

Post-2.3.7 regressions and safe update procedure

Plane AIO versions 2.3.7 to 2.4.1 introduced several documented regressions. Loss of real-time notifications when upgrading the Python server between these versions. 500 errors on outgoing webhooks if the webhook_trigger column is missing in the PostgreSQL migration — symptom: django.db.utils.ProgrammingError: column webhook_trigger does not exist in the plane-backend container logs. Slowdown of filter queries on workspaces with more than 5,000 issues (index regression fixed in 2.4.2).

Before any update from a version earlier than 2.4.2, back up your PostgreSQL database: docker compose exec -T db pg_dump -U plane plane > plane-backup-$(date +%F).sql. Then update: docker compose pull && docker compose up -d. If the backend does not start after the update, force the migrations manually: docker compose exec plane-backend python manage.py migrate --run-syncdb. For instances on 2.3.6 or earlier, a direct update to 2.4.2+ is recommended to skip the defective intermediate versions.

Troubleshooting: common errors and their fixes

Broken redirects or assets not loading after login. Typical symptom: the interface redirects to http://localhost or images and scripts fail to load. Cause: WEB_URL in the .env file does not match the actual public URL. Fix the value and restart the container.

Internal service startup failure. Typical message in docker logs plane: FATAL: api: exited too quickly. Check DATABASE_URL — an incorrect connection URL or a non-existent database name prevents migrations from running and causes this kind of fatal exit.

The /god-mode/ panel is inaccessible. Access to the admin interface requires creating the first account via the main interface first, then signing in with those credentials. If you disabled sign-ups before creating the first account, temporarily restore ENABLE_SIGNUP=1, create the account, then set it back to 0.

Invitation emails are not sent. Check that EMAIL_HOST and EMAIL_HOST_USER are set in .env and that the SMTP port (often 587 with STARTTLS or 465 with SSL) is reachable from your VPS. Test with docker exec plane python manage.py sendtestemail [email protected].

Degraded performance with many concurrent users. If Celery workers struggle to process tasks, increase VPS RAM before adjusting internal concurrency. The AIO image is configured for standard team use; a very high-load configuration requires switching to a multi-container setup with dedicated resources per component.

Keep control of your infrastructure without maintaining the OS layer

Self-hosting Plane shows that dependency on SaaS subscriptions is not inevitable: a stable image, a properly sized VPS and a TLS reverse proxy are enough for a professional-sized team. ServOrbit offers root VPS instances with dedicated IPv4, ready in minutes, with full root access to install Docker and manage your own tools. If you want to delegate the system layer — kernel updates, SSH hardening, backups — the VPS administration option lets you keep control of your data while outsourcing host maintenance. To go further in your DevOps practice, see the guide on automating your servers with Ansible and the Docker Compose guide for production.

Deploy Plane from the ServOrbit Marketplace

ServOrbit offers Plane pre-configured on VPS: PostgreSQL 16, Redis 7, RabbitMQ and MinIO set up automatically. Domain required, included in the recipe — first access in minutes, data under your control.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.