Run Plane project management with rootless Podman

Plane Community Edition on Debian 13, an open-source alternative to Jira and Linear with work items, boards and cycles, in rootless Podman under a user of its own behind Caddy, with Garage for attachments and telemetry off.

Tested on Plane 1.4.2 on Debian 13 (trixie) on a Melonslab server Updated October 14, 2026

Recommended server for this guide

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy · 2 vCPU · 8 GB Memory · 60 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€8.99/mo

Deploy now
On this page

What you will set up

Plane is a project management tool for teams, in the same family as Jira and Linear. Each project holds work items, Plane's name for tasks and issues, which you can show as a list, a board, a calendar, a spreadsheet or a timeline. Cycles group work into sprints of a week or two, modules group it by feature, and pages hold notes and specifications that several people can edit at the same time.

Plane is developed by Plane Software, Inc., which also sells a cloud service and paid editions. The Community Edition used here is open source under the GNU AGPL v3, with no limit on users, projects, work items or storage. The paid editions add, among other things, custom work item types and properties, templates, time tracking, finer roles, LDAP and custom reports; Billing & Plans in the workspace settings lists them. Logins in the Community Edition are by email and password, by a code sent by email, or through Google, GitHub, GitLab or Gitea.

Out of the box, Plane sends usage data to its developers, and its setup page has the box for it ticked. Step 8 switches it off, and step 12 shows what was sent and what is left.

Plane runs as thirteen containers: the web app, the admin pages (Plane calls them God mode), the public pages, a server for live editing, the API with two background workers, PostgreSQL, Valkey, RabbitMQ, S3 storage for attachments and Plane's own proxy in front. Plane's installer runs them with Docker Compose. Here the same images run as Quadlet units, in Podman under a user of its own called plane, behind Caddy from the Podman guide. They share a Podman network rather than a pod, because four of Plane's images listen on the same port, 3000, and in a pod they would collide.

For attachments Plane needs S3 storage. Its Compose file used to run MinIO, but the minio/minio image is no longer on Docker Hub, and Plane now uses pgsty/minio, a rebuild by the Pigsty project. This guide uses Garage instead, the S3 store from the Garage guide, made by Deuxfleurs, a non-profit association in France, and also open source under the AGPL. Plane worked with it unchanged.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • Plane 1.4.1 started with PostgreSQL 15, Valkey 7.2, RabbitMQ 3.13 and Garage 2.4.1, the instance admin was set up in God mode, and Plane was then updated to 1.4.2.
  • Sign-ups were open to anyone by default: a stranger could create an account until it was switched off. Afterwards, a sign-up got "Sign up disabled. Please contact your administrator."
  • A workspace and a project were created, with work items, a cycle and a page. On the board, a card dragged to In Progress changed state. Search found a work item by a word in its title.
  • A colleague was invited by email, created an account from the link while sign-ups were closed, and joined the workspace. With Plane pointed at a local test mail server, the invite, a test email and a password reset arrived. Sending through a real email provider was not tested.
  • A PNG and a PDF attached to a work item were stored in Garage and came back with the same checksums. A file over 5 MB was refused, and without a signed link the files could not be fetched.
  • Plane logged each visitor's real address, over IPv4 and IPv6, and ignored a forged X-Forwarded-For header.
  • A capture of the server's traffic showed the usage data going to telemetry.plane.so, and nothing after it was switched off, apart from a version check on GitHub when the API starts.
  • A backup was restored for a newly created plane user, with every work item and attachment back, and everything came back by itself after a reboot.

The containers used about 1.3 GB of memory together, most of it Plane's API, workers and live server. The images take about 3.3 GB of disk.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • an A record and an AAAA record for plane.example.com pointing at your server;
  • for invites and password resets by email, an account with an email provider that allows sending over SMTP.

The examples use plane.example.com for Plane and smtp.example.com for your provider's SMTP server. Replace them throughout.

1. Create the user

As root:

useradd -m -s /bin/bash plane
loginctl enable-linger plane
machinectl shell plane@

Everything up to step 7 runs as plane.

2. Create the secrets

for s in plane-secret-key plane-live-key plane-db-password plane-mq-password plane-s3-rpc; do
  openssl rand -hex 32 | tr -d '\n' | podman secret create $s -
done

That makes five random secrets, stored by Podman and not in the files below:

  • plane-secret-key is Plane's own key. It signs logins and invite links, and encrypts the SMTP password you enter in step 11. Step 13 saves a copy.
  • plane-live-key lets the live editing server talk to the API.
  • plane-db-password and plane-mq-password are the passwords for PostgreSQL and RabbitMQ.
  • plane-s3-rpc is Garage's internal secret.

3. Write the configuration

Create ~/plane.env, the settings the API and its workers share:

WEB_URL=https://plane.example.com
CORS_ALLOWED_ORIGINS=https://plane.example.com
POSTGRES_HOST=plane-db
POSTGRES_PORT=5432
POSTGRES_DB=plane
POSTGRES_USER=plane
REDIS_URL=redis://plane-redis:6379/
RABBITMQ_HOST=plane-mq
RABBITMQ_PORT=5672
RABBITMQ_USER=plane
RABBITMQ_VHOST=plane
USE_MINIO=1
AWS_REGION=garage
AWS_S3_ENDPOINT_URL=http://plane-minio:9000
AWS_S3_BUCKET_NAME=uploads
FILE_SIZE_LIMIT=5242880
GUNICORN_WORKERS=2

And ~/garage.toml, Garage's configuration:

metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
metadata_fsync = true
replication_factor = 1
rpc_bind_addr = "127.0.0.1:3901"
rpc_public_addr = "127.0.0.1:3901"

[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:9000"

What the settings do:

  • WEB_URL is the address Plane puts in links, invites and redirects. It must be the public https:// address. CORS_ALLOWED_ORIGINS lets browsers on that address use the API.
  • USE_MINIO=1 makes Plane send uploads and downloads through its own address, at https://plane.example.com/uploads/, where Plane's proxy passes them on to the storage. The browser never talks to Garage directly. plane-minio is the name the proxy expects the storage to have; step 4 gives it to Garage.
  • AWS_REGION must match s3_region in garage.toml, or Garage refuses the requests.
  • FILE_SIZE_LIMIT is the largest attachment in bytes, here 5 MB, Plane's default. To allow larger files, raise it here and in plane-proxy.container below.
  • In garage.toml, db_engine = "sqlite" with metadata_fsync = true keeps Garage's index of files safe if the server loses power, and replication_factor = 1 means one copy, the only choice on one server. The Garage guide explains the rest.

4. Define the containers

mkdir -p ~/.config/containers/systemd
cd ~/.config/containers/systemd

First the network they share. Create plane.network:

[Network]
NetworkName=plane

On it, each container can reach the others by name, such as plane-db. Then the four services Plane stores its data in. Create plane-db.container:

[Container]
ContainerName=plane-db
Image=docker.io/library/postgres:15-alpine
Network=plane.network
Exec=postgres -c max_connections=1000
Volume=plane-db:/var/lib/postgresql/data
Environment=POSTGRES_USER=plane POSTGRES_DB=plane
Secret=plane-db-password,type=env,target=POSTGRES_PASSWORD
HealthCmd=pg_isready -U plane
HealthInterval=10s
Notify=healthy
AutoUpdate=registry

[Service]
Restart=always

plane-redis.container, the cache:

[Container]
ContainerName=plane-redis
Image=docker.io/valkey/valkey:7.2-alpine
Network=plane.network
Volume=plane-redis:/data
AutoUpdate=registry

[Service]
Restart=always

plane-mq.container, the queue for background jobs such as emails:

[Container]
ContainerName=plane-mq
# RabbitMQ keeps its data under its host name: keep it the same
HostName=plane-mq
Image=docker.io/library/rabbitmq:3.13-alpine
Network=plane.network
Volume=plane-mq:/var/lib/rabbitmq
Environment=RABBITMQ_DEFAULT_USER=plane RABBITMQ_DEFAULT_VHOST=plane
Secret=plane-mq-password,type=env,target=RABBITMQ_DEFAULT_PASS
AutoUpdate=registry

[Service]
Restart=always

And plane-s3.container, Garage:

[Container]
ContainerName=plane-s3
# Plane's proxy sends uploads to this name
NetworkAlias=plane-minio
Image=docker.io/dxflrs/garage:v2.4.1
Exec=/garage server --single-node
Network=plane.network
Volume=%h/garage.toml:/etc/garage.toml:ro
Volume=plane-s3-meta:/var/lib/garage/meta
Volume=plane-s3-data:/var/lib/garage/data
Secret=plane-s3-rpc,type=env,target=GARAGE_RPC_SECRET
StopTimeout=30

[Service]
Restart=always

The versions are those Plane's own Compose file uses: PostgreSQL 15, Valkey 7.2 and RabbitMQ 3.13. Podman updates them within those versions (step 14). Garage has no tag that follows its releases, so its version is written out.

Now Plane itself. Its API image runs four ways: it sets up the database, serves the API, and runs two background workers. Those four files are the same apart from their name, their command and the [Unit] lines at the top. Create plane-migrator.container, which updates the database and stops:

[Unit]
Requires=plane-db.service
After=plane-db.service

[Container]
ContainerName=plane-migrator
Image=docker.io/makeplane/plane-backend:v1.4.2
Exec=./bin/docker-entrypoint-migrator.sh
Network=plane.network
EnvironmentFile=%h/plane.env
Secret=plane-secret-key,type=env,target=SECRET_KEY
Secret=plane-live-key,type=env,target=LIVE_SERVER_SECRET_KEY
Secret=plane-db-password,type=env,target=POSTGRES_PASSWORD
Secret=plane-mq-password,type=env,target=RABBITMQ_PASSWORD
Secret=plane-s3-key-id,type=env,target=AWS_ACCESS_KEY_ID
Secret=plane-s3-secret-key,type=env,target=AWS_SECRET_ACCESS_KEY

[Service]
Type=oneshot
RemainAfterExit=yes
TimeoutStartSec=900

plane-api.container:

[Unit]
Requires=plane-migrator.service plane-redis.service plane-mq.service
Wants=plane-s3.service
After=plane-migrator.service plane-redis.service plane-mq.service plane-s3.service

[Container]
ContainerName=plane-api
NetworkAlias=api
Image=docker.io/makeplane/plane-backend:v1.4.2
Exec=./bin/docker-entrypoint-api.sh
Network=plane.network
EnvironmentFile=%h/plane.env
Secret=plane-secret-key,type=env,target=SECRET_KEY
Secret=plane-live-key,type=env,target=LIVE_SERVER_SECRET_KEY
Secret=plane-db-password,type=env,target=POSTGRES_PASSWORD
Secret=plane-mq-password,type=env,target=RABBITMQ_PASSWORD
Secret=plane-s3-key-id,type=env,target=AWS_ACCESS_KEY_ID
Secret=plane-s3-secret-key,type=env,target=AWS_SECRET_ACCESS_KEY

[Service]
Restart=always

plane-worker.container:

[Unit]
Requires=plane-api.service
After=plane-api.service

[Container]
ContainerName=plane-worker
Image=docker.io/makeplane/plane-backend:v1.4.2
Exec=./bin/docker-entrypoint-worker.sh
Network=plane.network
EnvironmentFile=%h/plane.env
Secret=plane-secret-key,type=env,target=SECRET_KEY
Secret=plane-live-key,type=env,target=LIVE_SERVER_SECRET_KEY
Secret=plane-db-password,type=env,target=POSTGRES_PASSWORD
Secret=plane-mq-password,type=env,target=RABBITMQ_PASSWORD
Secret=plane-s3-key-id,type=env,target=AWS_ACCESS_KEY_ID
Secret=plane-s3-secret-key,type=env,target=AWS_SECRET_ACCESS_KEY

[Service]
Restart=always

plane-beat.container, which starts scheduled jobs:

[Unit]
Requires=plane-api.service
After=plane-api.service

[Container]
ContainerName=plane-beat
Image=docker.io/makeplane/plane-backend:v1.4.2
Exec=./bin/docker-entrypoint-beat.sh
Network=plane.network
EnvironmentFile=%h/plane.env
Secret=plane-secret-key,type=env,target=SECRET_KEY
Secret=plane-live-key,type=env,target=LIVE_SERVER_SECRET_KEY
Secret=plane-db-password,type=env,target=POSTGRES_PASSWORD
Secret=plane-mq-password,type=env,target=RABBITMQ_PASSWORD
Secret=plane-s3-key-id,type=env,target=AWS_ACCESS_KEY_ID
Secret=plane-s3-secret-key,type=env,target=AWS_SECRET_ACCESS_KEY

[Service]
Restart=always

Then the four parts that serve pages. plane-web.container, the app itself:

[Unit]
After=plane-api.service

[Container]
ContainerName=plane-web
NetworkAlias=web
Image=docker.io/makeplane/plane-frontend:v1.4.2
Network=plane.network

[Service]
Restart=always

plane-admin.container, God mode:

[Unit]
After=plane-api.service

[Container]
ContainerName=plane-admin
NetworkAlias=admin
Image=docker.io/makeplane/plane-admin:v1.4.2
Network=plane.network

[Service]
Restart=always

plane-space.container, for projects you publish:

[Unit]
After=plane-api.service

[Container]
ContainerName=plane-space
NetworkAlias=space
Image=docker.io/makeplane/plane-space:v1.4.2
Network=plane.network

[Service]
Restart=always

plane-live.container, for live editing of pages:

[Unit]
After=plane-api.service

[Container]
ContainerName=plane-live
NetworkAlias=live
Image=docker.io/makeplane/plane-live:v1.4.2
Network=plane.network
Environment=API_BASE_URL=http://api:8000 REDIS_HOST=plane-redis REDIS_PORT=6379 REDIS_URL=redis://plane-redis:6379/
Secret=plane-live-key,type=env,target=LIVE_SERVER_SECRET_KEY

[Service]
Restart=always

And last, Plane's proxy in front of them, plane-proxy.container:

[Unit]
Wants=plane-web.service plane-admin.service plane-space.service plane-live.service plane-worker.service plane-beat.service
After=plane-web.service plane-admin.service plane-space.service plane-live.service plane-api.service

[Container]
ContainerName=plane-proxy
Image=docker.io/makeplane/plane-proxy:v1.4.2
Network=plane.network
Environment=SITE_ADDRESS=:80 FILE_SIZE_LIMIT=5242880 BUCKET_NAME=uploads
# Only Caddy, on this server, can reach Plane: the port is not open to the internet.
PublishPort=127.0.0.1:8152:80

[Service]
Restart=always

[Install]
WantedBy=default.target

How it fits together:

  • The proxy is a small Caddy that Plane publishes with each release. It sends /api/ and /auth/ to the API, /god-mode/ to the admin pages, /spaces/ to the public pages, /live/ to the live server, /uploads/ to the storage, and everything else to the web app. It is the only container with a port, 8152, and only on 127.0.0.1. Keeping Plane's proxy means the routes stay right when a new release changes them. NetworkAlias gives each container the short name the proxy uses.
  • Only the proxy has an [Install] section. Starting it starts the rest, through the Wants and Requires lines, so one line starts Plane at boot.
  • The migrator runs before the API every time Plane starts, and applies any database changes a new version brings. The API waits for it. TimeoutStartSec gives a large update time to finish.
  • The Plane images have no AutoUpdate line: all five must be the same version, and an update can change the database. Step 14 updates them by hand.
  • The visitor's address reaches Plane in the X-Forwarded-For header. Caddy sets it, and replaces any such header the visitor sent, and Plane's proxy passes it on. That is why the API sees real addresses even though the containers are on a Podman network.

5. Start Garage and create the bucket

systemctl --user daemon-reload
systemctl --user start plane-s3
echo "alias garage='podman exec -e RUST_LOG=warn plane-s3 /garage'" >> ~/.bashrc
source ~/.bashrc
garage status

garage status lists one healthy node. Create the bucket for attachments and a key for Plane, with access to that bucket only, and store the key as two more secrets:

garage bucket create uploads
garage key create plane
garage bucket allow --read --write --owner uploads --key plane
garage key info plane --show-secret | awk '/^Key ID/ {printf "%s", $3}' | podman secret create plane-s3-key-id -
garage key info plane --show-secret | awk '/^Secret key/ {printf "%s", $3}' | podman secret create plane-s3-secret-key -

6. Start Plane

Download the Plane images first. They are about 2.7 GB, and downloading them as part of starting could take longer than systemd waits:

for i in backend frontend space admin live proxy; do
  podman pull docker.io/makeplane/plane-$i:v1.4.2
done

Then start it, and let Podman keep the database, cache and queue up to date:

systemctl --user start plane-proxy
systemctl --user enable --now podman-auto-update.timer

The first start takes about five minutes, while the migrator sets up the database, and the command waits until it is done. Plane is ready when this prints 200:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8152/api/instances/

podman logs plane-api ends with Listening at: http://0.0.0.0:8000, after Bucket 'uploads' exists..

7. Put Caddy in front

Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

plane.example.com {
    reverse_proxy 127.0.0.1:8152
}

Restart Caddy with systemctl --user restart caddy. Do step 8 right away: until an instance admin exists, the first person to open God mode becomes it.

8. Set up God mode

Open https://plane.example.com/god-mode/. Under Setup your Plane Instance, fill in your name, email address, Company name and a password with at least 8 characters, an upper-case letter, a lower-case letter, a number and a special character. Untick Allow Plane to anonymously collect usage events (step 12 explains it), and choose Continue. This account is the instance admin, and it is also your normal account in Plane.

God mode then opens on General. Go through these pages:

  • Authentication: switch off Allow anyone to sign up even without an invite. It is on from the start, and while it is on, anyone who finds your address can create an account. Afterwards only people you invite can sign up; others get "Sign up disabled. Please contact your administrator." Leave Passwords on. Unique codes needs email (step 11).
  • General: if you left the box at setup ticked, switch off Let Plane collect anonymous usage data under Telemetry and choose Save changes.

Artificial intelligence and Images in Plane ask for an OpenAI key and an Unsplash key. Leave them empty unless you want those services. Redirect to Plane at the bottom takes you to the app.

9. Create a workspace and invite your team

In the app, log in with the same email and password. Plane asks for your name, then for a workspace: give it a name, such as Example, and its address, plane.example.com/example, follows. Choose how many people will use it and Create workspace, and I'll do it later when it offers to invite people. Plane also puts a demo project with sample work items in the new workspace.

Now go back to God mode, at https://plane.example.com/god-mode/, and under Workspaces switch on Prevent anyone else from creating a workspace, so that people you invite join your workspace instead of making their own. Do it only after you have your workspace: with it on, Plane's first-time setup in the app stops you too, with "You don't seem to have any invites to a workspace and your instance admin has restricted creation of new workspaces".

To invite a colleague, open the workspace menu at the top left, then Settings, Members and Add member. Enter their email address, choose a role, such as Member, and Send invitations. They get an email with an Accept invite link, create their account from it, and are in the workspace. Without email (step 11), Plane still says "Invitations sent successfully", but nothing is sent: tell your colleague to sign up at https://plane.example.com/sign-up/ with the address you invited, which works even with sign-ups closed, and choose the workspace under Join invites or create a workspace.

Each user picks a time zone and a language under their profile, Preferences > Language & Time. The time zone starts as UTC, so set your own. Plane is translated into many languages, among them German, French and Polish, but not Swedish.

10. Projects, work items and cycles

On Projects, Add Project asks for a name and a short ID, such as WEB; work items are then numbered WEB-1, WEB-2 and so on. After Create project, Plane offers to switch on Cycles, Modules, Views and Intake, which are off by default. You can do it later under the project's Settings.

  • New work item creates one. Its page has a description, sub-items, links, comments, the state, assignees, priority, dates and labels.
  • The icons at the top of a project's work items switch between list, board, calendar, spreadsheet and timeline. On the board, each column is a state, from Backlog to Done, and you drag a card to move it.
  • Cycles > Add cycle creates a cycle with a start and end date. Open it and use Add existing work item to put work in it. It shows how far the work has come.
  • Attach on a work item uploads a file, up to 5 MB, the FILE_SIZE_LIMIT from step 3. Files are kept in Garage, and the links Plane gives out are signed and stop working after an hour.
  • Pages holds documents that several people can edit at the same time.
  • The search box at the top, Search commands..., finds work items by words in their title. In our test it did not find a word that was only in a description.

New projects get a random cover picture, which the browser loads from images.unsplash.com. You can change it under the project's Settings.

11. Email

Plane sends email for invites, password resets, codes for logging in without a password, and notifications about work items. Without email:

  • an invite says "Invitations sent successfully", but no email is sent (step 9 has the way around it);
  • Forgot your password? shows "Something went wrong. Please try again.";
  • Unique codes cannot be used to log in.

Plane keeps its SMTP settings in its database, where you enter them in God mode. It encrypts the password with plane-secret-key from step 2, so it is not in any file, but it cannot be a Podman secret. In God mode, open Email and switch it on, then fill in:

  • Host: smtp.example.com, your provider's SMTP server;
  • Port: 587;
  • Sender's email address: an address your provider lets you send from, such as plane@example.com;
  • Email security: TLS, which is STARTTLS on port 587. For port 465, choose SSL;
  • Username and Password: your account with the provider.

Choose Save changes, then Send test email, and enter your own address. We tested Plane's emails with a local test server; sending through a real provider was not tested.

Until email works, you can set a new password for a user from the server, as plane:

podman exec -it plane-api python manage.py reset_password colleague@example.com

It asks for the password twice, and refuses passwords that are easy to guess.

12. Outside connections

We captured the server's DNS lookups and outgoing connections while Plane was set up and used.

With usage data on, the API sends a report to telemetry.plane.so when it starts and every six hours. It holds the instance ID, the instance name (the Company name from setup), your Plane address, the version, the number of users, workspaces, projects, work items, cycles, modules and pages, and for each workspace its short name and the same counts. It holds no names of people or email addresses. With Let Plane collect anonymous usage data off in God mode, it stopped.

What is left:

  • Each time the API starts, it asks api.github.com for the latest Plane release, to show in God mode when an update is out. It sends nothing else, and it cannot be switched off.
  • In the browser, new projects load a cover picture from images.unsplash.com (step 10).
  • Invite emails show Plane's logo from media.docs.plane.so, which the recipient's email app loads.
  • Logins with Google, GitHub, GitLab or Gitea, the OpenAI and Unsplash keys, and webhooks contact those services only if you set them up. A webhook to another app on this same server, such as n8n, needs one of the fixes in step 8 of the Podman guide. Plane's containers share a network of their own, and both fixes work on such a network too.

13. Back up

As plane:

umask 077
mkdir -p ~/backup
podman exec plane-db pg_dump -U plane plane > ~/backup/plane-db.sql
systemctl --user stop plane-s3
podman volume export plane-s3-meta > ~/backup/plane-s3-meta.tar
podman volume export plane-s3-data > ~/backup/plane-s3-data.tar
systemctl --user start plane-s3
for s in $(podman secret ls --format '{{.Name}}'); do
  podman secret inspect --showsecret --format '{{.SecretData}}' $s > ~/backup/secret-$s
done
tar -cf ~/backup/config.tar plane.env garage.toml .config/containers/systemd

That saves:

  • the database, with every workspace, project, work item, comment, page and setting. The dump runs while Plane keeps working;
  • the attachments, in Garage's two volumes. Garage is stopped for the few seconds the export takes, so its files are consistent, and uploads fail during those seconds;
  • the secrets, of which plane-secret-key is the one you cannot do without: it decrypts the SMTP password and keeps logins valid;
  • the configuration and unit files.

Valkey and RabbitMQ hold only a cache and jobs in progress, and need no backup. Copy ~/backup to another machine, for example with restic, and keep it private: it holds your data and the secrets.

To restore on a new server, set it up with step 1, copy the backup to /home/plane/backup and run chown -R plane: /home/plane/backup as root. Then, as plane:

tar -xf ~/backup/config.tar -C ~
for f in ~/backup/secret-*; do tr -d '\n' < "$f" | podman secret create "${f#*/secret-}" -; done
podman volume create plane-s3-meta
podman volume import plane-s3-meta ~/backup/plane-s3-meta.tar
podman volume create plane-s3-data
podman volume import plane-s3-data ~/backup/plane-s3-data.tar
systemctl --user daemon-reload
systemctl --user start plane-db
podman exec -i plane-db psql -q -U plane plane < ~/backup/plane-db.sql

Then pull the images and start Plane as in step 6, and add Caddy's block as in step 7. Starting only the database first loads the backup before the migrator sets up an empty one.

To restore on the same server instead, into empty volumes, stop Plane and remove the old data first, then run the volume and database lines above:

systemctl --user stop 'plane-*'
podman volume rm plane-db plane-s3-meta plane-s3-data

14. Update

podman auto-update updates PostgreSQL, Valkey and RabbitMQ once a day within their versions, and restarts Plane around them. Plane itself you update by hand. Read the release notes on Plane's GitHub page first, and take a backup. Then, as plane, with the version you run and the new one in the sed line:

for i in backend frontend space admin live proxy; do
  podman pull docker.io/makeplane/plane-$i:v1.4.3
done
cd ~/.config/containers/systemd
sed -i 's/:v1.4.2/:v1.4.3/' plane-*.container
systemctl --user daemon-reload
systemctl --user restart plane-migrator plane-web plane-admin plane-space plane-live plane-proxy
podman image prune -af

Restarting the migrator restarts the API and the workers after it, once the database is updated. We updated from 1.4.1 to 1.4.2 this way. Plane was back after about a minute, and its API reported the new version.

Troubleshooting

502 Bad Gateway for a few minutes after starting. The migrator is still setting up or updating the database, and the API waits for it. journalctl --user -u plane-migrator shows its progress, and podman logs plane-api says Listening at when the API is up.

Attachments fail to upload. Check that podman logs plane-api has Bucket 'uploads' exists.. If it says Access to the bucket 'uploads' is forbidden, the key in the secrets is not the one Garage has: compare garage key info plane with podman secret inspect --showsecret plane-s3-key-id. AWS_REGION in plane.env must be garage.

"Could not send email. Please check your configuration" after Send test email. Plane could not reach the SMTP server or log in. Check the host, the port and Email security: TLS for port 587, SSL for port 465.

"Something went wrong. Please try again." after Forgot your password?. Email is not set up. Set it up as in step 11, or set a new password with reset_password.

Run it on your own server

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy

€8.99/mo

vCPU
2
Memory
8 GB
Storage
60 GB
Transfer
10 TB
Performance
SSD · RAID 1
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides