# Deploy Plane with the plane-project-management template

Source: https://docs.quake.ai/resources/deployments/deploy-plane-project-management-template
Markdown: https://docs.quake.ai/resources/deployments/deploy-plane-project-management-template.md

---

# Deploy Plane with the plane-project-management template

Stand up [Plane](https://plane.so), an open-source project-management platform, on a single Quake AI instance using the [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) `plane-project-management`. You apply the template, point a domain at the host and serve it over HTTPS, set the public URL and run the one-shot database migration, create the MinIO uploads bucket, start Plane, sign up the first admin account, and create a workspace, a project, and an issue.

Plane keeps your team's issues, cycles, and projects on infrastructure you own. You run it yourself; this is a self-hosted tool you operate, not a managed multi-tenant service.

<Figure size="md" caption="What you'll build: a Plane host on a single instance with bundled PostgreSQL, Redis, RabbitMQ, and MinIO, reached over HTTPS through a Caddy reverse proxy">

```d2
direction: right

user: Team member {shape: person}
fip: Floating IP
instance: Ubuntu instance {
  caddy: Caddy\nreverse proxy
  proxy: Plane\nproxy
  apps: Plane\napp + worker
  db: PostgreSQL
  redis: Redis
  mq: RabbitMQ
  minio: MinIO
  caddy -> proxy: proxies 443 to 8080
  proxy -> apps: routes to web, api, space, admin, live
  apps -> db: issues, cycles, projects
  apps -> redis: jobs + cache
  apps -> mq: task queue
  apps -> minio: file uploads
}

user -> fip: HTTPS
fip -> instance.caddy
```

</Figure>

<PricingCompanion
  components={[
    { kind: "template", slug: "plane-project-management", required: true },
  ]}
/>

## Prerequisites

You need:

- OpenTofu 1.6.0 or later (or Terraform 1.6.0 or later) installed locally.
- Your OpenStack credentials sourced into the shell (`source openrc.sh`). See [the OpenStack CLI guide](/docs/tools/openstack-cli).
- An SSH keypair that already exists in your project. Record its name for the `key_name` variable.
- A copy of the `plane-project-management` template directory from [the template reference page](/resources/iac-templates/plane-project-management).
- A domain you can point at the instance. Plane needs a stable public URL for auth callbacks and workspace links.

## Step 1: Apply the template

Copy the template's example variables file and set `key_name`:

```bash
cp terraform.tfvars.example terraform.tfvars
```

```hcl
key_name = "YOUR_KEY_NAME"
```

Initialize, preview, and apply:

```bash
tofu init
tofu plan
tofu apply
```

OpenTofu provisions a private network, a router, a security group, a block volume mounted at `/var/lib/docker`, an instance, and a floating IP. On first boot, cloud-init installs Docker Engine, generates `SECRET_KEY`, the PostgreSQL password, the RabbitMQ password, and the MinIO root credentials into `/opt/plane/.env`, and starts PostgreSQL, Redis, RabbitMQ, and MinIO. Plane's app, worker, and proxy containers start after you finish configuration in the next steps.

Read the outputs and record `floating_ip` and `app_url`:

```bash
tofu output
```

## Step 2: Point a domain at the host and serve HTTPS with Caddy

Plane builds absolute links and auth callbacks from its public URL, so it needs a domain with TLS before you sign up your first account.

1. Create a DNS **A record** for your domain (for example `plane.example.com`) pointing at `YOUR_FLOATING_IP`. Follow [How to point a domain at a Quake AI resource](/docs/network/how-to/point-domain-to-quake-ai). Wait until it resolves:

```bash
dig +short plane.example.com
```

2. SSH to the instance and create `/opt/plane/Caddyfile`:

```text
plane.example.com {
  reverse_proxy 127.0.0.1:8080
}
```

3. Add Caddy to `/opt/plane/docker-compose.yml`:

```yaml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    network_mode: host
    volumes:
      - /opt/plane/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
volumes:
  caddy_data:
```

For background on certificates, see [How to issue and auto-renew a TLS certificate with Let's Encrypt](/docs/network/how-to/lets-encrypt-certificate).

## Step 3: Set WEB_URL, migrate, and start Plane

Edit `/opt/plane/.env` on the instance and set the public URL and CORS origin:

```text
WEB_URL=https://plane.example.com
CORS_ALLOWED_ORIGINS=https://plane.example.com
```

Run the one-shot database migration, create the MinIO bucket Plane uploads files into, then start the app, worker, and proxy containers:

```bash
cd /opt/plane
docker compose run --rm migrator
docker compose exec plane-minio mc alias set local http://plane-minio:9000 plane YOUR_MINIO_PASSWORD
docker compose exec plane-minio mc mb local/uploads
docker compose up -d
docker compose ps
```

`YOUR_MINIO_PASSWORD` is the `AWS_SECRET_ACCESS_KEY` value cloud-init generated into `/opt/plane/.env`. Confirm every container reports healthy before continuing.



Plane's `api`, `worker`, and `beat-worker` containers all use the same `makeplane/plane-backend` image with different startup commands; none of them create the database schema on their own. The `migrator` service runs Django's migrations once and exits (`restart: "no"`), so it belongs before the app containers start, not alongside them.



## Step 4: Sign up and create your first workspace

1. Open `https://plane.example.com`. The first account to sign up becomes the instance's first admin.
2. Create a **workspace** (for example `payments-team`). A workspace is Plane's top-level container for projects.
3. Inside the workspace, create a **project** (for example `checkout-service`).
4. Add an **issue**: set a title, a priority, and an assignee.

## Step 5: Invite a teammate

1. Go to **Workspace Settings** > **Members** > **Invite members**.
2. Enter a teammate's email. Plane sends an invite they accept with their own password (or you configure Google or GitHub OAuth later, separately from the steps above).
3. Add them to the `checkout-service` project with a role scoped to what they need.

## What you built

- **Applied the `plane-project-management` template** to provision a network, security group, data volume, instance, and floating IP, with PostgreSQL, Redis, RabbitMQ, and MinIO started by cloud-init
- **Served Plane over HTTPS** by pointing a domain at the floating IP and routing it through a Caddy reverse proxy
- **Ran the one-shot database migration** and created the MinIO uploads bucket
- **Started the app, worker, and proxy containers**
- **Signed up the first admin account** and created a workspace, a project, and an issue
- **Invited a teammate**

## Scope of this deployment

This template runs a single-VM Plane host, not a managed multi-tenant project-management service. The instance is CPU-only and runs in one region, and it bundles PostgreSQL, Redis, RabbitMQ, and MinIO as containers on the same host. You operate the instance, Docker, Plane's app and worker containers, the datastores, and the data volume yourself: back them up, patch them, and snapshot the volume before you resize or rebuild. For a larger team, move PostgreSQL and the other datastores onto their own instances and size the app host up.

## Next steps

- [Plane project-management template](/resources/iac-templates/plane-project-management): the template reference, parameters, and resource map
- [Self-managed PostgreSQL template](/resources/iac-templates/self-managed-postgres): the database to point at when you outgrow the bundled one
- [Deploy Infisical with the infisical-secrets template](/resources/deployments/deploy-infisical-secrets-template): a lighter self-hosted ops tool from the same track
- [Security hardening checklist](/docs/security/hardening-checklist): tighten SSH access and exposure before you invite the rest of the team

## Clean up

When you no longer need the deployment, destroy everything the template created:

```bash
tofu destroy
```

Then remove the DNS A record you created in step 2. Because Plane, its datastores, and MinIO's uploads all live on the instance and its attached volume, `tofu destroy` removes them along with the infrastructure. Export any issues or attachments you want to keep first.
