Deployment guide

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

Deploy on a VPS Cloud →

Tutorial

Automation

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

Your Linear or Jira bill grows with every new seat, and handing your project tickets to a third-party vendor is not always acceptable. Plane is an alternative under the AGPL-3.0 license, distributed as a self-hostable Community edition with an official Docker Compose installer. This guide shows how to deploy it on a Linux VPS, put it behind an HTTPS reverse proxy, understand what the AIO image really requires, and diagnose the failures that keep coming back in public issues: a missing MinIO image, a redirect loop on the profile page, and problems after a version upgrade.

13 min read8 steps
Contents· Why host Plane yourself instead of paying per seat1/11
  1. 01Why host Plane yourself instead of paying per seat
  2. 02What you gain by hosting Plane on your own VPS
  3. 03Sized prerequisites for a stable deployment
  4. 04Deploy Plane Community in eight steps
  5. 05Post-installation configuration: the variables that matter
  6. 06Object storage: the missing MinIO image and the fix in release 1.4.3
  7. 07After a version upgrade: what issue 8708 establishes, and what it does not
  8. 08Compose installer or AIO image: what each one requires
  9. 09Troubleshooting: redirect loop between /profile/ and /?next_path=/profile/
  10. 10Troubleshooting: other frequent errors and their fixes
  11. 11Keep control of your infrastructure without maintaining the system layer

Why host Plane yourself instead of paying per seat

An issue tracker holds the most talkative part of a team: decisions, attachments, customer names, deadlines. Hosting it on your VPS moves the cost from the number of seats to the capacity of the server: you pay for resources, not for users. Plane has more than 61,000 stars on GitHub, its code is published under AGPL-3.0, and the vendor reports on its blog more than 100,000 Docker deployments and more than 44,000 Kubernetes deployments, which gives an idea of the installed base and of how much public field experience is available.

There is a trade-off to know before you start. The Community stack has several services: web interface, API, workers, real-time server, PostgreSQL database, Valkey cache, RabbitMQ message queue, S3-compatible object storage and a reverse proxy. The official installer orchestrates them with Docker Compose, but you remain responsible for backups, upgrades and the state of the object storage. The sections below detail each point, separating what the maintainers have confirmed from what only users have reported.

What you gain by hosting Plane on your own VPS

  • A cost tied to the machine, not to seats — the VPS price does not change with the number of accounts; what limits the team is memory and disk.
  • Data under your control — tickets, comments, files and members stay in your PostgreSQL database and your object storage, on your own disk.
  • An AGPL-3.0 license — the code can be audited; the license requires you to publish your changes if you expose a modified version to users.
  • Issues, cycles, modules, pages and inbox — task tracking, sprints, functional groupings, light documentation and incoming requests, with no separate module.
  • An official installer — the setup.sh script fetches a docker-compose.yaml and a plane.env published with the release, then handles start, stop, upgrade and backup.
  • Upgrades at your own pace — you pick the moment of the version upgrade, after a backup, and the script archives the old Compose file before replacing it.
  • Storage you can move out — the PostgreSQL database and S3 storage can live outside the VPS; Plane's documentation recommends it for production.
  • A documented REST API — useful to sync issues from a CI pipeline or from GitHub.

Sized prerequisites for a stable deployment

Plane's documentation states 2 cores and 4 GB of RAM as a minimum, with 8 GB recommended in production. Plan for 2 vCPU and 8 GB of RAM if the team exceeds about ten people or makes heavy use of pages and cycles: PostgreSQL, RabbitMQ, the workers and the API share the same machine. Provide 20 GB of disk or more: Docker images and the database are small at first, it is the uploaded files that make the uploads volume grow.

You need a recent Docker with the Compose plugin (the script calls docker compose), a domain name or subdomain whose A record points to the VPS, and ports 80 and 443 open. A TLS certificate is essential in production: without HTTPS, passwords and sessions travel in clear text.

Deploy Plane Community in eight steps

  1. Prepare the host and install Docker

    On an up-to-date Debian or Ubuntu VPS, install Docker with the official script: curl -fsSL https://get.docker.com | sh. Enable the service with systemctl enable --now docker, then check docker version and docker compose version. Open ports 80 and 443 in the firewall and restrict port 22 to your own addresses.

  2. Download the official installer

    Create a dedicated folder with mkdir -p /opt/plane-selfhost && cd /opt/plane-selfhost, then download the script from the latest release: curl -fsSL -o setup.sh https://github.com/makeplane/plane/releases/latest/download/setup.sh and chmod +x setup.sh. The script works in the current folder: it creates the plane-app subfolder there.

  3. Run the installation

    Run ./setup.sh as root or as a member of the docker group, then choose option 1 (Install). The script downloads docker-compose.yaml and plane.env into plane-app/ and, in recent versions, generates credentials for the bundled object storage. Leave the menu with option 8 before configuring: the documentation asks you to stop the stack before editing plane.env.

  4. Fill in plane.env

    Open plane-app/plane.env and set at least APP_DOMAIN=plane.your-domain.com, WEB_URL=https://plane.your-domain.com and CORS_ALLOWED_ORIGINS=https://plane.your-domain.com. To let Nginx hold ports 80 and 443, add LISTEN_HTTP_PORT=8080. Check that SECRET_KEY and LIVE_SERVER_SECRET_KEY hold unique values, never examples: openssl rand -hex 32 produces one.

  5. Start the stack and check the containers

    Run ./setup.sh again and choose option 2 (Start). The first start downloads the images, then the migrator container prepares the database and exits. Check the state with docker compose -f plane-app/docker-compose.yaml --env-file plane-app/plane.env ps: all the other containers must be running. Option 6 of the menu shows the logs.

  6. Get a TLS certificate with Certbot

    Install Nginx and Certbot with apt install -y nginx certbot python3-certbot-nginx, then request the certificate: certbot certonly --nginx -d plane.your-domain.com. The files are placed in /etc/letsencrypt/live/plane.your-domain.com/. The domain name must already point to the VPS.

  7. Configure Nginx as an HTTPS reverse proxy

    Create an Nginx site that listens on port 443 with the certificate you obtained and forwards everything to http://127.0.0.1:8080. Add the Host, X-Real-IP, X-Forwarded-For and X-Forwarded-Proto headers, as well as Upgrade and Connection for the real-time server WebSockets. A second server block redirects port 80 to HTTPS with return 301 https://$host$request_uri;. Enable the site, then nginx -t && systemctl reload nginx. Docker publishes port 8080 without going through UFW: close it from the outside with your provider's firewall.

  8. Create the instance administrator and the team

    Open https://plane.your-domain.com/god-mode/ to create the instance administrator account. Keep the trailing slash: one user reports that an address without it stayed stuck, a point the maintainers have not confirmed. Then open the application, create the workspace and the first project, and invite your collaborators.

Post-installation configuration: the variables that matter

WEB_URL and CORS_ALLOWED_ORIGINS: the documentation asks you to set both to the address the browser actually uses. It is the first thing to check when behavior looks odd after login.

LISTEN_HTTP_PORT and LISTEN_HTTPS_PORT: ports published by the bundled proxy, 80 and 443 by default. Change them if Nginx already holds the standard ports.

TRUSTED_PROXIES and NUM_PROXIES: they determine the client IP address seen by the API, hence rate limiting. Behind your own reverse proxy, the installer README asks you to raise NUM_PROXIES by one per extra hop; left empty, the default behavior applies.

UNSPLASH_ACCESS_KEY: optional, it feeds the cover image picker. Leave it empty if you do not use Unsplash; it is the first setting to re-read in case of a redirect loop, described below.

APP_RELEASE: the release being followed, stable at first. The script resolves it to a version number at install time and on every upgrade.

Stop the stack with option 3 before editing plane.env, then restart with option 4.

Before any upgrade, back up: option 7 of the script, plus a PostgreSQL dump with docker compose -f plane-app/docker-compose.yaml --env-file plane-app/plane.env exec -T plane-db pg_dump -U plane plane > plane-backup-$(date +%F).sql. Then run option 5 (Upgrade): the script compares the installed release with the latest one, asks for confirmation, archives the old docker-compose.yaml in plane-app/archive/ and keeps a plane.env.bak copy. Re-read plane.env afterwards to spot new variables. Never upgrade with docker compose down -v: the -v option deletes the uploads volume, hence your attachments. The release notes of v1.4.3, published in October 2026, fix several access control flaws and ask self-hosted instances to upgrade.

Object storage: the missing MinIO image and the fix in release 1.4.3

Symptom. On a fresh install or an upgrade, the image download fails on the storage service with the message pull access denied for minio/minio, repository does not exist or may require 'docker login'. The registry answers 401 rather than 404 to an anonymous client, so the message looks like a credentials problem: no docker login will change anything.

Confirmed by the Plane team. The minio organization is gone from Docker Hub. PR #9829, merged in September 2026, notes it and switches the images to quay.io. The release notes of v1.4.3 then state that the bundled object storage uses the pgsty/silo image, pinned to a release tag rather than latest. A repository contributor also stated in issue #9834, opened in September 2026, that the release had been updated with another image.

Reported by users only. The quay.io/minio/minio workaround worked for some, then several participants reported 401 errors on that registry from September 24, 2026. Those reports predate the release fix; do not apply them to a recent installation.

What to do. On a fresh install, use the script from the latest release, then check with grep -n 'image:' plane-app/docker-compose.yaml that no line mentions minio/minio. On an existing install that still mentions it, back up then run option 5 of the script. For production, PR #9829 itself recommends favoring external S3 storage over the bundled MinIO, whose community branch is frozen upstream according to its author: this is a direction from the Plane team, not a guarantee.

After a version upgrade: what issue 8708 establishes, and what it does not

In March 2026, a user opened issue #8708 to describe a difficult installation. After moving from version 2.3.3 to 2.3.7, the permissions of uploaded files were broken; a volume declared with single quotes in a file of the AIO image was parsed incorrectly; and the first button offered to the administrator led to /god-mode without a trailing slash, a page that stayed stuck.

What the maintainers confirmed fits in one sentence: version 2.3.7 is not an official release, it was produced for a specific customer, and 2.4.0 fixes the problem. They did not confirm any of the three anomalies. The author replied that the quote defect persisted in the Dockerfile, and the issue was closed with no associated fix.

Keep three rules. Install only releases published on the repository's Releases page, the latest being v1.4.3; do not pull an intermediate version. Back up before every version upgrade. After the upgrade, open an old attachment and a recent one: if opening fails, inspect the rights of the uploads volume with docker volume inspect before changing anything, keeping the backup within reach.

Compose installer or AIO image: what each one requires

Scroll the table

CriterionCompose installer (Community)Community AIO image
Services includedPostgreSQL, Valkey, RabbitMQ, object storage and proxy in the Compose stackApplications, API, workers and Caddy proxy in a single container
External servicesNone required; recommended in productionPostgreSQL, Redis, RabbitMQ and S3 storage to provide yourself
Entry point`./setup.sh` and the `plane.env` file`docker run` with `DOMAIN_NAME`, `DATABASE_URL`, `REDIS_URL`, `AMQP_URL` and the S3 variables
SecretsValues to check in `plane.env``SECRET_KEY` and `LIVE_SERVER_SECRET_KEY` required: the container stops if they are empty
UpgradeOption 5 of the script, archiving the old Compose filePull the new image and recreate the container
Described useSingle server, external storage advised in productionSimple deployment and testing, according to its README

Troubleshooting: redirect loop between /profile/ and /?next_path=/profile/

Symptom (issue #6694, opened in March 2025, still open on October 24, 2026). Behind a domain name, opening https://your-domain.com/profile/ redirects to https://your-domain.com/?next_path=/profile/, then the page loops. In the author's API log, almost every request answers 200, except one that comes back on each turn: GET /api/unsplash/?query= HTTP/1.1" 401, followed by Unauthorized: /api/unsplash/.

Established. The author used Docker Compose with an external Nginx in front of Plane. The maintainers asked for the deployment mode, then whether a reverse proxy was in place, and replied in March 2025 that they were investigating. They have published no cause.

Reported. In July 2026, a participant found the same symptom: they had entered the Unsplash secret key instead of the access key in UNSPLASH_ACCESS_KEY. A PR proposed by an external contributor, #10108, still open on October 31, 2026, puts forward the explanation: the API returns Unsplash's 401 as is and the web application treats any 401 as an expired session, hence the redirect; it proposes answering 502 instead. This is a contributor's hypothesis, backed by a single testimony, not a finding by the maintainers, and it does not necessarily cover every redirect loop.

What to do, in order. First, look for the line in the log: docker compose -f plane-app/docker-compose.yaml --env-file plane-app/plane.env logs api | grep unsplash. If it does not appear, your loop has another cause: re-read WEB_URL and CORS_ALLOWED_ORIGINS. If it appears with a 401, re-read UNSPLASH_ACCESS_KEY in plane.env and in the Images section of the /god-mode/ panel: you need the Access Key, not the secret key. If you do not use Unsplash, empty the value and restart the stack; according to the PR, a missing key returns an empty list with no error.

Troubleshooting: other frequent errors and their fixes

Redirects to http://localhost or resources that do not load. WEB_URL and CORS_ALLOWED_ORIGINS do not match the public address. Fix both, then stop and restart the stack.

pull access denied for minio/minio. See the object storage section: move to a recent release with option 5 of the script.

502 Bad Gateway returned by Nginx. The bundled proxy is not listening on the targeted port. Compare LISTEN_HTTP_PORT in plane.env with the proxy_pass target, then test curl -I http://127.0.0.1:8080 from the VPS. If the answer is slow, docker compose -f plane-app/docker-compose.yaml --env-file plane-app/plane.env ps shows which container keeps restarting.

Invitation emails do not go out. Fill in the SMTP server in the instance email settings, from /god-mode/, then send a test invitation. Also check that the host does not filter the outgoing SMTP port.

Slowness with many users. Watch docker stats: if memory is saturated, increase RAM before any other tuning, then consider moving PostgreSQL and object storage off the VPS.

Keep control of your infrastructure without maintaining the system layer

Hosting Plane yourself shows that depending on SaaS subscriptions is not inevitable: the official installer, a correctly sized VPS and a TLS reverse proxy are enough for a professional-size team. ServOrbit offers root VPS with a dedicated IPv4 address, ready in a few minutes, on which you have full root access to install Docker and run 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 production guide.

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.