Workload · Running your team's project tracker

Run Plane project management on a server your team owns

Plane project management runs happily on your own server through its official Docker Compose setup. For the Commercial install Plane lists two processor cores with 4 GB of memory, and 8 GB once real work lives on it; its Community instructions want an AWS t3.medium or bigger, a 2 vCPU, 4 GB instance. Here is the whole job in order, from choosing an edition to restoring a backup, with the safety steps the quick-start leaves out.

The short version
  1. Two cores and 4 GB at least (t3.medium-class for Community); 8 GB advised for production
  2. Community Edition is open source under AGPL v3.0, no licence key
  3. You become the admin over a private tunnel before going live
  4. Passwords and keys live in plane.env, readable by root only
  5. Nightly backups with Plane's own script, restored with restore.sh

Is self-hosting Plane right for your team?

It is if you want your issues, cycles and pages on a machine you control and someone on the team is happy to look after it. Plane's own planning guide puts the first install at one to three working days for one person, then a small ongoing slice of someone's time for updates and backups.

Next, pick an edition, because each has its own installer:

  • Community Edition. Open source under the AGPL v3.0 licence. Plane's own summary: "Free, no license key, full source available, you can audit and modify it." It offers what Plane's hosted Free plan offers. Using and changing it inside your own company is fine; if you offer it to outside customers as a service, the AGPL asks you to publish your changes.
  • Commercial Edition. Closed source. In Plane's words, "It includes a built-in Free tier of 12 user seats per workspace", and paid plans are unlocked with a licence key that checks in with Plane. Paid plans are only available here, so moving off Community is the route to them.
  • Not a fit if your team wants new features the day they ship, since Community gets them last, after Plane's hosted service and Commercial. Nor if a single machine pausing for maintenance is unacceptable: for high availability Plane points to Kubernetes, deployed with Helm.

This guide uses the Community Edition.

What your server needs

Plan for the 8 GB figure rather than the 4 GB minimum. Out of the box, one machine runs everything: the web app and its admin and sharing front ends, the API, background workers, a real-time editing service, and the services that hold your data.

  • Processor and memory: the Commercial install's list says two cores plus 4 GB, and 8 GB is recommended once it's in production (64-bit Intel/AMD and ARM); the Community instructions name an AWS t3.medium, 2 vCPUs and 4 GB, as the smallest machine.
  • Database: PostgreSQL 16.x, or 15 at version 15.7 or later; the bundle ships 15.7.
  • Helpers: Valkey (a Redis-compatible cache) and RabbitMQ (a job queue), both bundled.
  • Files: MinIO, a bundled S3-style store; an outside S3-compatible service also works.
  • Search: handled by the database. OpenSearch-powered advanced search belongs to Commercial's paid plans, so you won't set it up here.

Plane publishes no per-person sizing, so we don't guess one. Watch memory in the first weeks and leave room to grow.

Why we save the install script before running it

Plane's Commercial instructions feed an installer from prime.plane.so directly into sh. Please don't run it that way. You would be executing code you have not seen, and if the download stalls part-way, a half-received script still runs. Save it first (curl -fsSL https://prime.plane.so/install/ > plane-prime-installer.sh), open it, and when you are happy, start it with bash plane-prime-installer.sh; bash is the interpreter it declares.

What you will find is short. It reads your machine ID, sends it to prime.plane.so as a header while downloading a program called prime-cli, unpacks that into /bin using sudo and starts its setup. prime-cli itself is closed source, so this is as far as reading can take you; afterwards it manages Commercial installs in /opt/plane.

The Community instructions already save setup.sh to a file, which makes checking it easy. It is a readable bash menu: it fetches the Compose file and settings template for the latest release, confirms images exist for your processor, and starts, stops, upgrades and backs up the stack.

Up and running, step by step

  1. Switch the firewall on (SSH only) before anything listens, then download Plane's two scripts into a folder only root can read.
  2. Read setup.sh in full before you run it, and restore.sh too (less restore.sh) before you ever need it.
  3. Run the install, lock down plane.env, and replace every default secret. The template ships plane as the database and queue passwords and access-key/secret-key for file storage. Note that the Compose file builds its own connection addresses with those defaults when DATABASE_URL or AMQP_URL are left empty, so fill both in with the new passwords. Do this before the first start: the database keeps the password it was born with.
  4. Start Plane with its proxy reachable only from the server itself, and check that nothing answers on a public address yet.
# run as root; Docker Engine and its Compose plugin are already installed
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw   # so the SSH rule covers IPv6 too
ufw allow OpenSSH
ufw --force enable
apt-get install -y jq
umask 077
mkdir -p /srv/plane
cd /srv/plane
curl -fsSL -o setup.sh https://github.com/makeplane/plane/releases/latest/download/setup.sh
curl -fsSL -o restore.sh https://raw.githubusercontent.com/makeplane/plane/refs/heads/preview/deployments/cli/community/restore.sh
less setup.sh                   # read it; press q to quit

Now, before the next block, go to /srv/plane and run bash setup.sh install by itself and wait for it. It downloads the Compose file and plane.env into plane-app and pulls the images; it only pauses with a question if your processor has no ready-made images.

cd /srv/plane/plane-app
chmod 600 plane.env
dbpw=$(openssl rand -hex 24); mqpw=$(openssl rand -hex 24)
sed -i \
 -e "s|^SECRET_KEY=.*|SECRET_KEY=$(openssl rand -hex 32)|" \
 -e "s|^LIVE_SERVER_SECRET_KEY=.*|LIVE_SERVER_SECRET_KEY=$(openssl rand -hex 32)|" \
 -e "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$dbpw|" \
 -e "s|^DATABASE_URL=.*|DATABASE_URL=postgresql://plane:$dbpw@plane-db:5432/plane|" \
 -e "s|^RABBITMQ_PASSWORD=.*|RABBITMQ_PASSWORD=$mqpw|" \
 -e "s|^AMQP_URL=.*|AMQP_URL=amqp://plane:$mqpw@plane-mq:5672/plane|" \
 -e "s|^AWS_ACCESS_KEY_ID=.*|AWS_ACCESS_KEY_ID=$(openssl rand -hex 12)|" \
 -e "s|^AWS_SECRET_ACCESS_KEY=.*|AWS_SECRET_ACCESS_KEY=$(openssl rand -hex 32)|" \
 -e "s|^WEB_URL=.*|WEB_URL=http://localhost:9080|" \
 -e "s|^CORS_ALLOWED_ORIGINS=.*|CORS_ALLOWED_ORIGINS=http://localhost:9080|" \
 plane.env
unset dbpw mqpw
# keep the proxy on 127.0.0.1 for now (adds host_ip under both ports)
sed -i 's/^\( *\)mode: host$/&\n\1host_ip: 127.0.0.1/' docker-compose.yaml
cd /srv/plane && bash setup.sh start
ss -tlnp | grep -E ':(80|443) '

Every secret is generated on the server, so none lands in your shell history. They do show up in the process list for the split second sed runs; if other people log in to this machine, edit plane.env in a text editor instead. Hex strings are deliberate: Plane's docs warn against special characters in the database password.

A firewall surprise worth knowing: over IPv4, Docker opens the proxy's ports with its own rules, ahead of ufw, so a ufw rule can neither open nor close 80 and 443. Over IPv6 it depends on your Docker version and settings. When Docker leaves ip6tables alone, ufw does filter IPv6, so add ufw allow 80,443/tcp ahead of creating an AAAA record; otherwise Let's Encrypt cannot reach you over IPv6. Whatever your setup, check both ports from outside on IPv4 and IPv6.

Make yourself the admin before anyone else can

The first person to finish Plane's setup screen becomes instance admin and gets the /god-mode area, so make sure that person is you.

  1. On your laptop, sign in with your usual SSH account and forward a port: ssh -N -L 9080:127.0.0.1:80 admin@203.0.113.10 (use your own account name and server address).
  2. Browse to http://localhost:9080/god-mode/, enter your email and a strong password.
  3. Under Authentication, turn off sign-ups without an invite if only invited people should join. Under General you can stop the anonymous usage data.
  4. Point your domain at the server. In plane.env, APP_DOMAIN and SITE_ADDRESS get the plain domain; CORS_ALLOWED_ORIGINS and WEB_URL both get its https:// address; and CERT_EMAIL gets the word email, a space, then your address, inside double quotes, e.g. CERT_EMAIL="email ops@yourcompany.com", as the template's comment says. The quotes matter because setup.sh reads plane.env as a shell script, where an unquoted space would turn the address into a command.
  5. Delete the temporary binding with sed -i '/host_ip:/d' /srv/plane/plane-app/docker-compose.yaml, then restart from the install folder: cd /srv/plane; bash setup.sh restart. The bundled Caddy proxy now serves ports 80 and 443 publicly and requests its certificate from Let's Encrypt.

Prefer your own nginx? Plane's Community notes describe removing the bundled proxy and routing each path to its container.

Email invites and file uploads

Set up email in God mode under Email straight away, or invites and password resets will not reach anyone. You enter the host, port, sender address, the security type (TLS, SSL or none) and, ideally, a username and password; Plane supports password-based SMTP login only, not OAuth.

Attachments land in the bundled MinIO, in a bucket called uploads. The two AWS_* keys in plane.env are also MinIO's admin login, and the proxy passes /uploads straight to MinIO: left at their defaults, they would be a published login. Uploads are capped at 5 MB until you raise FILE_SIZE_LIMIT (in bytes). To keep files elsewhere, set USE_MINIO=0 and fill in the AWS_* settings for any S3-compatible service, such as MinIO running on a second machine.

Keeping your work safe: backups and updates

Plane's script does the backing up: bash setup.sh backup packs the database, uploads and cache data into dated tar.gz files under /srv/plane/plane-app/backup. The docs name those three; reading the current script, we found it saves the queue (RabbitMQ) data as well. It does not include plane.env, and without that file the data is hard to use, so save a copy of it somewhere safe as well. To run it every night, this creates a small cron file that sets a strict umask and logs each run to a dated file:

# creates /etc/cron.d/plane-backup: one line, nightly at 03:15, logged to a dated file
mkdir -p -m 700 /var/log/plane-backup
printf '%s\n' '15 3 * * * root umask 077; cd /srv/plane && bash setup.sh backup > /var/log/plane-backup/$(date +\%F).log 2>&1' > /etc/cron.d/plane-backup
cat /etc/cron.d/plane-backup     # check it shows \% before F
  • Copy each backup and plane.env to another place, and delete old folders now and then; the script never cleans up.
  • The script copies the database files while it is running. For extra peace of mind, we suggest a nightly pg_dump taken inside the plane-db container and saved to a file.
  • Back up before every update, as the docs ask. Coming from a version below 0.14.0? Step through 0.14.0 first.
  • Updating: download setup.sh again and look it over. Then, from /srv/plane, run bash setup.sh upgrade by itself, because it waits for you to confirm.

Plane's upgrade docs describe an older routine: compare a downloaded variables-upgrade.env with your plane.env by hand. The script as it ships now, by our reading, does that for you: it keeps your old file as plane.env.bak, puts the new template in place as plane.env and copies your values into it. Afterwards, diff /srv/plane/plane-app/plane.env.bak /srv/plane/plane-app/plane.env reveals any new keys, chmod 600 /srv/plane/plane-app/plane.env tightens the file again, and then bash setup.sh start restarts Plane.

Bringing a backup back wipes and rebuilds the Plane data volumes from the folder you name, so double-check it first:

  1. Stop Plane from its folder, /srv/plane, with bash setup.sh stop. restore.sh refuses to run while Plane is up.
  2. Restore, replacing the example date with your folder's name: bash /srv/plane/restore.sh /srv/plane/plane-app/backup/YYYYMMDD-HHMM.
  3. Start it again from the same folder: bash setup.sh start.

The server that fits

Your tracker shares a machine with nobody on a Primcast dedicated server: all its cores, memory and disks are yours, you log in as root, and the port is unmetered. Ready servers are running within 30 minutes of approval, you get IPv6 at no charge for the AAAA record, and rack servers and blades include console access, handy if a firewall change ever locks you out. You manage the server; we keep the hardware and the network running.

Many teams put their code next to their tracker. See Gitea on your own server, or Mattermost for team chat.

Questions people ask

Do we have to pay to self-host Plane?
Not for the Community Edition: it is AGPL v3.0 open source and needs no licence key. The Commercial Edition also has a free plan, with 12 user seats per workspace. Paid plans exist only in Commercial.
Will 4 GB of memory be enough?
It is the minimum the Docker Compose page lists for the Commercial install, where 8 GB is advised for production use; Plane's Community instructions start at an AWS t3.medium, 2 vCPUs with 4 GB. Everything, including the database and file storage, runs on the same machine by default. Start at 8 GB if the team will use it every day.
What happens if someone reaches the setup page before me?
Then they, not you, would hold the instance admin role, because the first finished setup claims it. That is why this guide keeps the proxy bound to 127.0.0.1 and has you finish setup through an SSH tunnel. Only then does the domain go public.
Can we switch to the paid edition later?
Yes. Plane documents an upgrade from Community to Commercial, and that is the only path to paid plans. Plane calls such moves generally one-directional in practice, so take a backup first.
Does the Commercial Edition work without internet access?
Its licence checks talk to Plane's Prime portal, so plan for outbound access. Plane offers a separate Airgapped Edition for offline networks, with Enterprise Grid only.

Give your team's tracker its own server

Choose a dedicated server with the memory Plane needs and have it running within 30 minutes of approval.

Get a server for your Plane tracker

Read next