mirror of
https://github.com/runyanjake/olomana.git
synced 2026-06-25 08:04:52 -07:00
add Plane project management software
This commit is contained in:
parent
5c3875fe27
commit
031c6fa556
32
productivity/project-management/plane/.env.example
Normal file
32
productivity/project-management/plane/.env.example
Normal file
@ -0,0 +1,32 @@
|
||||
# ── Public URL ────────────────────────────────────────────────────────────────
|
||||
DOMAIN=plane.whitney.rip
|
||||
|
||||
# ── Plane image tag ───────────────────────────────────────────────────────────
|
||||
# Pin to a release (e.g. v1.3.0) or use `stable` to follow latest.
|
||||
# See: https://github.com/makeplane/plane/releases
|
||||
APP_RELEASE=v1.3.0
|
||||
|
||||
# ── Django / backend ──────────────────────────────────────────────────────────
|
||||
# Generate a fresh 50-char random string, e.g.:
|
||||
# openssl rand -hex 25
|
||||
SECRET_KEY=replace-me-with-a-long-random-string
|
||||
|
||||
# Gunicorn workers for the API container
|
||||
GUNICORN_WORKERS=2
|
||||
|
||||
# Max upload size in bytes (default 5 MiB)
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# ── Postgres ──────────────────────────────────────────────────────────────────
|
||||
POSTGRES_DB=plane
|
||||
POSTGRES_USER=plane
|
||||
POSTGRES_PASSWORD=change-me
|
||||
|
||||
# ── RabbitMQ ──────────────────────────────────────────────────────────────────
|
||||
RABBITMQ_USER=plane
|
||||
RABBITMQ_PASSWORD=change-me
|
||||
|
||||
# ── MinIO (object storage for uploads) ────────────────────────────────────────
|
||||
MINIO_ROOT_USER=change-me-access-key
|
||||
MINIO_ROOT_PASSWORD=change-me-secret-key
|
||||
MINIO_BUCKET_NAME=uploads
|
||||
1
productivity/project-management/plane/.gitignore
vendored
Normal file
1
productivity/project-management/plane/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.env
|
||||
173
productivity/project-management/plane/README.md
Normal file
173
productivity/project-management/plane/README.md
Normal file
@ -0,0 +1,173 @@
|
||||
# Plane
|
||||
|
||||
[Plane](https://plane.so) is an open-source project management tool — issues,
|
||||
cycles (sprints), modules, pages, and views. Think of it as a self-hosted
|
||||
alternative to Jira / Linear / Asana.
|
||||
|
||||
- Website: <https://plane.so>
|
||||
- Source: <https://github.com/makeplane/plane>
|
||||
- Docs: <https://docs.plane.so>
|
||||
- Self-hosting reference: <https://developers.plane.so/self-hosting/overview>
|
||||
|
||||
This stack runs the community edition behind Traefik at
|
||||
<https://plane.whitney.rip>.
|
||||
|
||||
## Architecture
|
||||
|
||||
The upstream release ships a bundled `plane-proxy` (Caddy) that routes
|
||||
subpaths to the right internal service. We keep that proxy in place and only
|
||||
expose **it** to the external `traefik` network — Traefik terminates TLS and
|
||||
forwards to `plane-proxy:80`, which fans out inside the `plane` bridge
|
||||
network:
|
||||
|
||||
| Path | Service | What it is |
|
||||
|---------------|-----------------|---------------------------------------------|
|
||||
| `/` | `web` | Main Next.js application |
|
||||
| `/spaces/*` | `space` | Public-shared views (read-only share links) |
|
||||
| `/god-mode/*` | `admin` | Instance admin UI (see below) |
|
||||
| `/live/*` | `live` | Real-time collaboration server |
|
||||
| `/api/*` | `api` | Django/DRF backend |
|
||||
| `/auth/*` | `api` | Auth endpoints |
|
||||
| `/uploads/*` | `plane-minio` | Object storage (attachments, avatars) |
|
||||
|
||||
Supporting services: Postgres (`plane-db`), Valkey (`plane-redis`), RabbitMQ
|
||||
(`plane-mq`), MinIO (`plane-minio`), plus a Celery `worker` + `beat-worker`
|
||||
and a one-shot `migrator`.
|
||||
|
||||
## The `/god-mode` admin page
|
||||
|
||||
Plane splits its UI across two Next.js apps:
|
||||
|
||||
- `web` serves the normal app at `/` — this is what end-users see.
|
||||
- `admin` serves the **instance-admin UI** at `/god-mode`.
|
||||
|
||||
`/god-mode` is where you, the operator, configure the Plane instance itself:
|
||||
initial superuser, SMTP, SSO/OAuth providers (Google, GitHub, GitLab, Gitea),
|
||||
sign-up toggles, and license keys. It is **not** a workspace admin — each
|
||||
workspace has its own settings accessible from within the `web` app.
|
||||
|
||||
On first launch, hit <https://plane.whitney.rip/god-mode> before anything
|
||||
else to create the instance admin. If you forget and create a normal user
|
||||
first, you can promote them later via `docker compose exec api python
|
||||
manage.py shell`.
|
||||
|
||||
## First-time setup
|
||||
|
||||
1. **Copy the env template and fill it in.**
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
$EDITOR .env
|
||||
```
|
||||
|
||||
At minimum, set:
|
||||
- `DOMAIN=plane.whitney.rip`
|
||||
- `SECRET_KEY` — generate with `openssl rand -hex 25`
|
||||
- `POSTGRES_PASSWORD`, `RABBITMQ_PASSWORD`
|
||||
- `MINIO_ROOT_USER`, `MINIO_ROOT_PASSWORD`
|
||||
|
||||
`.env` is gitignored; only `.env.example` is committed.
|
||||
|
||||
2. **Ensure the external Traefik network exists.**
|
||||
|
||||
```bash
|
||||
docker network inspect traefik >/dev/null \
|
||||
|| docker network create traefik
|
||||
```
|
||||
|
||||
3. **Create the data directories.** All persistent state lives on the host
|
||||
under `/pwspool/software/plane/`:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /pwspool/software/plane/{db,redis,rabbitmq,minio,logs/{api,worker,beat}}
|
||||
```
|
||||
|
||||
4. **Pull images and start the stack.**
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
The `migrator` and `plane-createbuckets` containers run once and exit —
|
||||
that is expected. Tail logs until things settle:
|
||||
|
||||
```bash
|
||||
docker compose logs -f api web proxy
|
||||
```
|
||||
|
||||
5. **Create the instance admin.** Browse to
|
||||
<https://plane.whitney.rip/god-mode>, set your admin email/password,
|
||||
then configure auth providers and SMTP.
|
||||
|
||||
6. **Create your first workspace.** Visit
|
||||
<https://plane.whitney.rip>, sign in with the admin account, and create
|
||||
a workspace. Invite users from within the workspace settings.
|
||||
|
||||
## Normal use
|
||||
|
||||
```bash
|
||||
# Start / stop
|
||||
docker compose up -d
|
||||
docker compose down # stop; data persists under /pwspool
|
||||
docker compose restart api worker # restart a subset
|
||||
|
||||
# Status & logs
|
||||
docker compose ps
|
||||
docker compose logs -f api
|
||||
docker compose logs -f proxy
|
||||
|
||||
# Pull latest images for the pinned APP_RELEASE and re-up
|
||||
docker compose pull && docker compose up -d
|
||||
|
||||
# Upgrade to a newer Plane release
|
||||
# 1. Check https://github.com/makeplane/plane/releases
|
||||
# 2. Bump APP_RELEASE in .env (e.g. v1.3.0 -> v1.4.0)
|
||||
# 3. Pull + up — migrator re-runs automatically
|
||||
docker compose pull && docker compose up -d
|
||||
|
||||
# Shell into a running service
|
||||
docker compose exec api bash
|
||||
docker compose exec plane-db psql -U "$POSTGRES_USER" "$POSTGRES_DB"
|
||||
|
||||
# Run a Django management command
|
||||
docker compose exec api python manage.py shell
|
||||
docker compose exec api python manage.py reset_password <email>
|
||||
```
|
||||
|
||||
## Data layout
|
||||
|
||||
```
|
||||
/pwspool/software/plane/
|
||||
├── db/ # Postgres PGDATA
|
||||
├── redis/ # Valkey persistence
|
||||
├── rabbitmq/ # RabbitMQ mnesia
|
||||
├── minio/ # MinIO object store (uploads bucket)
|
||||
└── logs/
|
||||
├── api/
|
||||
├── worker/
|
||||
└── beat/
|
||||
```
|
||||
|
||||
For backups, at minimum capture:
|
||||
- `pg_dump` of the `plane` database (cleaner than tar'ing `db/`)
|
||||
- `minio/` (attachments, avatars)
|
||||
|
||||
## Notes & gotchas
|
||||
|
||||
- **Password strength is hardcoded.** Plane requires a zxcvbn score of 3
|
||||
(strong) plus 8-character minimum. There is no env var to relax this —
|
||||
use a passphrase or a password manager.
|
||||
- **`plane-proxy` is Caddy**, not nginx (despite the historical
|
||||
`NGINX_BASE_DOMAIN` variable in older docs). It needs `SITE_ADDRESS`,
|
||||
`BUCKET_NAME`, and `FILE_SIZE_LIMIT`. In our setup Caddy listens on plain
|
||||
HTTP :80 and Traefik terminates TLS in front.
|
||||
- **`certresolver=lets-encrypt`** in the Traefik labels matches the
|
||||
convention used by the sibling stacks (`taiga`, `planka`). Adjust if your
|
||||
Traefik uses a different resolver name.
|
||||
- **Uploads live in MinIO** inside the stack (`USE_MINIO=1`). To use
|
||||
external S3, flip `USE_MINIO` to `0` in `docker-compose.yml`'s
|
||||
`x-backend-env` block and override the `AWS_*` vars.
|
||||
- Upstream docker-compose / variables files are published per release at
|
||||
<https://github.com/makeplane/plane/releases/latest> — useful as a
|
||||
reference when new knobs appear.
|
||||
299
productivity/project-management/plane/docker-compose.yml
Normal file
299
productivity/project-management/plane/docker-compose.yml
Normal file
@ -0,0 +1,299 @@
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
plane:
|
||||
driver: bridge
|
||||
|
||||
# Shared environment for all plane-backend services
|
||||
x-backend-env:
|
||||
&backend-env
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@plane-db/${POSTGRES_DB}
|
||||
REDIS_URL: redis://plane-redis:6379/
|
||||
AMQP_URL: amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@plane-mq:5672/
|
||||
WEB_URL: https://${DOMAIN}
|
||||
CORS_ALLOWED_ORIGINS: https://${DOMAIN}
|
||||
DOCKERIZED: 1
|
||||
GUNICORN_WORKERS: ${GUNICORN_WORKERS:-2}
|
||||
USE_MINIO: 1
|
||||
AWS_REGION: us-east-1
|
||||
AWS_ACCESS_KEY_ID: ${MINIO_ROOT_USER}
|
||||
AWS_SECRET_ACCESS_KEY: ${MINIO_ROOT_PASSWORD}
|
||||
AWS_S3_ENDPOINT_URL: http://plane-minio:9000
|
||||
AWS_S3_BUCKET_NAME: ${MINIO_BUCKET_NAME:-uploads}
|
||||
FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880}
|
||||
|
||||
services:
|
||||
|
||||
# ── Database ─────────────────────────────────────────────────────────────────
|
||||
|
||||
plane-db:
|
||||
image: postgres:15.7-alpine
|
||||
container_name: plane-db
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
PGDATA: /var/lib/postgresql/data
|
||||
volumes:
|
||||
- /pwspool/software/plane/db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# ── Cache ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
plane-redis:
|
||||
image: valkey/valkey:7.2.11-alpine
|
||||
container_name: plane-redis
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
volumes:
|
||||
- /pwspool/software/plane/redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "redis-cli ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# ── Message Queue ─────────────────────────────────────────────────────────────
|
||||
|
||||
plane-mq:
|
||||
image: rabbitmq:3.13.6-management-alpine
|
||||
container_name: plane-mq
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER}
|
||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
|
||||
RABBITMQ_DEFAULT_VHOST: /
|
||||
volumes:
|
||||
- /pwspool/software/plane/rabbitmq:/var/lib/rabbitmq
|
||||
healthcheck:
|
||||
test: ["CMD", "rabbitmq-diagnostics", "check_running", "-q"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
# ── Object Storage (MinIO) ────────────────────────────────────────────────────
|
||||
|
||||
plane-minio:
|
||||
image: minio/minio:latest
|
||||
container_name: plane-minio
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
command: server /export --console-address ":9090"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
volumes:
|
||||
- /pwspool/software/plane/minio:/export
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
|
||||
plane-createbuckets:
|
||||
image: minio/mc:latest
|
||||
container_name: plane-createbuckets
|
||||
restart: "no"
|
||||
networks:
|
||||
- plane
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
|
||||
MINIO_BUCKET_NAME: ${MINIO_BUCKET_NAME:-uploads}
|
||||
depends_on:
|
||||
plane-minio:
|
||||
condition: service_healthy
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
mc alias set myminio http://plane-minio:9000 $$MINIO_ROOT_USER $$MINIO_ROOT_PASSWORD &&
|
||||
mc mb myminio/$$MINIO_BUCKET_NAME --ignore-existing"
|
||||
|
||||
# ── Migrations (one-shot) ─────────────────────────────────────────────────────
|
||||
|
||||
migrator:
|
||||
image: makeplane/plane-backend:${APP_RELEASE:-stable}
|
||||
container_name: plane-migrator
|
||||
restart: "no"
|
||||
networks:
|
||||
- plane
|
||||
command: ./bin/docker-entrypoint-migrator.sh
|
||||
environment:
|
||||
<<: *backend-env
|
||||
depends_on:
|
||||
plane-db:
|
||||
condition: service_healthy
|
||||
plane-redis:
|
||||
condition: service_healthy
|
||||
|
||||
# ── API ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
api:
|
||||
image: makeplane/plane-backend:${APP_RELEASE:-stable}
|
||||
container_name: plane-api
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
command: ./bin/docker-entrypoint-api.sh
|
||||
environment:
|
||||
<<: *backend-env
|
||||
volumes:
|
||||
- /pwspool/software/plane/logs/api:/logs
|
||||
depends_on:
|
||||
plane-db:
|
||||
condition: service_healthy
|
||||
plane-redis:
|
||||
condition: service_healthy
|
||||
plane-mq:
|
||||
condition: service_healthy
|
||||
migrator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
# ── Celery Worker ─────────────────────────────────────────────────────────────
|
||||
|
||||
worker:
|
||||
image: makeplane/plane-backend:${APP_RELEASE:-stable}
|
||||
container_name: plane-worker
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
command: ./bin/docker-entrypoint-worker.sh
|
||||
environment:
|
||||
<<: *backend-env
|
||||
volumes:
|
||||
- /pwspool/software/plane/logs/worker:/logs
|
||||
depends_on:
|
||||
plane-db:
|
||||
condition: service_healthy
|
||||
plane-redis:
|
||||
condition: service_healthy
|
||||
plane-mq:
|
||||
condition: service_healthy
|
||||
migrator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
# ── Celery Beat ───────────────────────────────────────────────────────────────
|
||||
|
||||
beat-worker:
|
||||
image: makeplane/plane-backend:${APP_RELEASE:-stable}
|
||||
container_name: plane-beat-worker
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
command: ./bin/docker-entrypoint-beat.sh
|
||||
environment:
|
||||
<<: *backend-env
|
||||
volumes:
|
||||
- /pwspool/software/plane/logs/beat:/logs
|
||||
depends_on:
|
||||
plane-db:
|
||||
condition: service_healthy
|
||||
plane-redis:
|
||||
condition: service_healthy
|
||||
plane-mq:
|
||||
condition: service_healthy
|
||||
migrator:
|
||||
condition: service_completed_successfully
|
||||
|
||||
# ── Frontend ──────────────────────────────────────────────────────────────────
|
||||
|
||||
web:
|
||||
image: makeplane/plane-frontend:${APP_RELEASE:-stable}
|
||||
container_name: plane-web
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
environment:
|
||||
NEXT_PUBLIC_API_BASE_URL: https://${DOMAIN}
|
||||
NEXT_PUBLIC_SPACE_BASE_URL: https://${DOMAIN}/spaces
|
||||
NEXT_PUBLIC_ADMIN_BASE_URL: https://${DOMAIN}/god-mode
|
||||
NEXT_PUBLIC_LIVE_BASE_URL: https://${DOMAIN}/live
|
||||
depends_on:
|
||||
- api
|
||||
|
||||
# ── Admin Dashboard ───────────────────────────────────────────────────────────
|
||||
|
||||
admin:
|
||||
image: makeplane/plane-admin:${APP_RELEASE:-stable}
|
||||
container_name: plane-admin
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
environment:
|
||||
NEXT_PUBLIC_API_BASE_URL: https://${DOMAIN}
|
||||
NEXT_PUBLIC_ADMIN_BASE_URL: https://${DOMAIN}/god-mode
|
||||
depends_on:
|
||||
- api
|
||||
|
||||
# ── Public Space ──────────────────────────────────────────────────────────────
|
||||
|
||||
space:
|
||||
image: makeplane/plane-space:${APP_RELEASE:-stable}
|
||||
container_name: plane-space
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
environment:
|
||||
NEXT_PUBLIC_API_BASE_URL: https://${DOMAIN}
|
||||
NEXT_PUBLIC_SPACE_BASE_URL: https://${DOMAIN}/spaces
|
||||
depends_on:
|
||||
- api
|
||||
|
||||
# ── Live (real-time collaboration) ────────────────────────────────────────────
|
||||
|
||||
live:
|
||||
image: makeplane/plane-live:${APP_RELEASE:-stable}
|
||||
container_name: plane-live
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
environment:
|
||||
LIVE_BASE_PATH: /live
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
API_BASE_URL: http://api:8000
|
||||
depends_on:
|
||||
- api
|
||||
|
||||
# ── Proxy (gateway – only service on the Traefik network) ─────────────────────
|
||||
|
||||
proxy:
|
||||
image: makeplane/plane-proxy:${APP_RELEASE:-stable}
|
||||
container_name: plane-proxy
|
||||
restart: always
|
||||
networks:
|
||||
- plane
|
||||
- traefik
|
||||
environment:
|
||||
# plane-proxy is Caddy — it needs SITE_ADDRESS (listen spec),
|
||||
# BUCKET_NAME, and FILE_SIZE_LIMIT. Traefik terminates TLS in front
|
||||
# of it, so Caddy listens plain-HTTP on :80 and issues no certs.
|
||||
SITE_ADDRESS: ":80"
|
||||
BUCKET_NAME: ${MINIO_BUCKET_NAME:-uploads}
|
||||
FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT:-5242880}
|
||||
CERT_EMAIL: ""
|
||||
CERT_ACME_DNS: ""
|
||||
TRUSTED_PROXIES: "0.0.0.0/0"
|
||||
depends_on:
|
||||
- web
|
||||
- api
|
||||
- admin
|
||||
- space
|
||||
- live
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- traefik.http.routers.plane.rule=Host(`${DOMAIN}`)
|
||||
- traefik.http.routers.plane.tls=true
|
||||
- traefik.http.routers.plane.tls.certresolver=lets-encrypt
|
||||
- traefik.http.services.plane.loadbalancer.server.port=80
|
||||
- traefik.docker.network=traefik
|
||||
Loading…
x
Reference in New Issue
Block a user