Plane, Self-Hosted: Owning My Ticketing
My whole workflow runs on tickets. I forked open-source Plane, tore out Docker, deployed it fully native on bare metal, and added the features the commercial edition paywalls.
The Problem
I needed project management for my development work but didn’t want another SaaS subscription or someone else’s server holding my data. Plane’s open-source edition was about 80% of what I wanted - good enough to use, not good enough to leave alone. The commercial edition locked features I needed behind a paywall, and the Docker self-hosting path was sluggish and painful to operate.
What I Built
A self-hosted fork of Plane’s Community Edition, pinned at v1.2.3, that I own and run in production with all my real data migrated across from the commercial box. It runs faster, costs nothing per month, and does things the commercial edition charges for - bulk delete and AI-assisted ticket creation among them.
The native deployment is the core of it. The stock path runs everything in Docker under compose. I ran a four-way runtime bake-off - Podman Compose (the painful baseline), a slim native design, a Podman Quadlet hybrid, and full native - and full native won outright. The production box runs PostgreSQL 15, Redis, RabbitMQ, and nginx directly on an LXC container, with the Django backend and Next.js frontend built from source and systemd managing the services. No container runtime in the stack at all. Restart times dropped under three seconds, and moving off Docker raised API throughput by 50% - which matters, because my AI agents hit the REST API constantly and were getting rate-limited under the old setup.
Tech Stack
- Python
- TypeScript
- Bash
- Django
- Next.js
- React
- PostgreSQL 15
- Redis
- RabbitMQ
- Cloudflare R2
- LXC / Proxmox
- systemd
- nginx
- Cloudflare Tunnel
- pytest
- custom stdlib load-test harness
How It Works
Beyond the runtime swap, I added what I actually needed. Bulk delete - the original reason for forking - went back in. An AI-enhanced ticketing path lets me paste raw text (notes, ideas, bug descriptions) and get back a well-formed, repo-aware ticket, which keeps traceability and documentation consistent without hand-formatting every issue.
The commercial and community editions diverge at the database level. I mapped the gap - one nullable column and three dropped commercial-only columns - into a repeatable migration runbook with a SQL bridge script, proved it on a copy of live data, then test-loaded the real database onto the new box before cutting over. I also modularized the OpenAPI utilities (cutting decorator code roughly 80%) and rebuilt the data export system around a typed, schema-based architecture with CSV, JSON, and XLSX output. A stdlib-only load-testing harness (hammer.py) exercised fetch-all, list, single-read, and write workloads across concurrency ladders to validate the box before I trusted production data to it.
What Made It Hard
Migrating real data without losing or corrupting a row. Adding an API to someone else’s schema without a database migration. And proving the native box had the headroom to run my daily workflow, which is where the load-test harness earned its keep.
The Throughline
This is the ticketing backend the rest of the work runs on. Every project I track and every ticket my agents create flows through it - it’s the system Latticeflow writes to as it builds the other projects on this site. Owning the tool means I shape it around my workflow instead of the reverse.
Status
Live and serving as my primary project management system since the cutover on June 15, 2026, at plane.throughlinetech.net via Cloudflare Tunnel. The old commercial instance is retired and powered off. The migration is proven and repeatable, the box survives reboots cleanly, and the load-test results give me confidence in its headroom. I pull upstream changes selectively and apply fork patches on top.