mirror of
https://github.com/runyanjake/olomana.git
synced 2026-06-25 16:14:52 -07:00
Compare commits
No commits in common. "e45d794be61582268c38445cca15697f405ee622" and "069535fefcecd1b7acd6b373ac9670497273474e" have entirely different histories.
e45d794be6
...
069535fefc
@ -1,52 +0,0 @@
|
|||||||
# Mail Server
|
|
||||||
A dockerized mail server for PWS. For now I am using [Stalwart](https://stalw.art/).
|
|
||||||
|
|
||||||
# Setup
|
|
||||||
|
|
||||||
1. Port forward the following ports:
|
|
||||||
- 25/TCP - SMTP, mandatory for receiving mail. May need to open a support ticket to open this port or live with partial nonfunction.
|
|
||||||
- 465 - SMTPS, implicit TLS.
|
|
||||||
- 587 - SMTP, alternative, possibly blocked by ISPs.
|
|
||||||
- 993 - IMAPS, mandatory for reading mail from apps.
|
|
||||||
- 443 - HTTPS, mandatory for the management dashboard and modern JMAP.
|
|
||||||
|
|
||||||
2. DNS Records (Basic)
|
|
||||||
- A, `Host=mail, v=YOUR_IP`
|
|
||||||
- MX, `Host=@, v=mail.example.com`
|
|
||||||
- CNAME, `em123.example.com -> sendgrid.net`
|
|
||||||
- CNAME, `s1._domainkey.example.com`
|
|
||||||
- CNAME, `s2._domainkey.example.com`
|
|
||||||
|
|
||||||
3. DNS Records (Security)
|
|
||||||
- TXT (DKIM), `Host=@, v=spf1 mx include:sendgrid.net -all`.
|
|
||||||
- TXT (DMARC), `Host=_dmarc, v=DMARC1; p=quarantine; rua=mailto:admin@example.com`, Tells people what to do if SPF/DKIM fails.
|
|
||||||
|
|
||||||
4. Configure Sendgrid/Get API Key
|
|
||||||
- We are NOT setting a PTR record because I am on a residential internet connection.
|
|
||||||
- Set up a SendGrid account.
|
|
||||||
- Go to `Settings > API Keys` and create a key with `Full Access` and `Mail Send` permissions. Copy the key.
|
|
||||||
- Go to `Settings > Sender Authentication` and complete Sender Authentication steps to link your domain to SendGrid so they can send your mail.
|
|
||||||
|
|
||||||
5. Create Relay Host in Stalwart (`Settings > SMTP > Outbound > Relay Hosts`)
|
|
||||||
- `Description: SendGrid Relay`
|
|
||||||
- `Address: smtp.sendgrid.net`
|
|
||||||
- `Port: 587`
|
|
||||||
- `Protocol: SMTP`
|
|
||||||
- `Authentication: Username: apikey, Secret: <YOUR_SENDGRID_API_KEY>`.
|
|
||||||
|
|
||||||
6. Set Routing Rule in Stalwart (`Settings > SMTP > Outbound > Routing')
|
|
||||||
- Create or Edit the routing strategy:
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{ "if": "is_local_domain('', rcpt_domain)", "then": "local" },
|
|
||||||
{ "else": "SendGrid Relay" }
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Start via Docker
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Test
|
|
||||||
Use [Mail-Tester.com](https://mail-tester.com) to test.
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
# ── 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,173 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@ -1,299 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
DOMAIN=
|
|
||||||
SECRET_KEY=
|
|
||||||
TAIGA_SCHEME=https
|
|
||||||
WEBSOCKETS_SCHEME=wss
|
|
||||||
SUBPATH=
|
|
||||||
|
|
||||||
# Admin credentials (not used in docker-compose; set via manage.py createsuperuser)
|
|
||||||
TAIGA_ADMIN_USERNAME=
|
|
||||||
TAIGA_ADMIN_EMAIL=
|
|
||||||
TAIGA_ADMIN_PASSWORD=
|
|
||||||
|
|
||||||
# Postgres
|
|
||||||
POSTGRES_DB=taiga
|
|
||||||
POSTGRES_USER=
|
|
||||||
POSTGRES_PASSWORD=
|
|
||||||
|
|
||||||
# Async RabbitMQ (used by Celery workers)
|
|
||||||
TAIGA_ASYNC_RABBITMQ_USER=
|
|
||||||
TAIGA_ASYNC_RABBITMQ_PASSWORD=
|
|
||||||
TAIGA_ASYNC_RABBITMQ_VHOST=taiga
|
|
||||||
TAIGA_ASYNC_RABBITMQ_HOST=taiga-async-rabbitmq
|
|
||||||
|
|
||||||
# Events RabbitMQ (used by taiga-events WebSocket service)
|
|
||||||
TAIGA_EVENTS_RABBITMQ_USER=
|
|
||||||
TAIGA_EVENTS_RABBITMQ_PASSWORD=
|
|
||||||
TAIGA_EVENTS_RABBITMQ_VHOST=taiga
|
|
||||||
TAIGA_EVENTS_RABBITMQ_HOST=taiga-events-rabbitmq
|
|
||||||
|
|
||||||
# Shared RabbitMQ Erlang cookie (any secret string)
|
|
||||||
RABBITMQ_ERLANG_COOKIE=
|
|
||||||
|
|
||||||
# Email (set EMAIL_BACKEND=console to log to stdout instead of sending)
|
|
||||||
EMAIL_BACKEND=console
|
|
||||||
EMAIL_HOST=
|
|
||||||
EMAIL_PORT=587
|
|
||||||
EMAIL_HOST_USER=
|
|
||||||
EMAIL_HOST_PASSWORD=
|
|
||||||
EMAIL_USE_TLS=True
|
|
||||||
EMAIL_USE_SSL=False
|
|
||||||
|
|
||||||
# Telemetry
|
|
||||||
ENABLE_TELEMETRY=False
|
|
||||||
@ -1 +0,0 @@
|
|||||||
.env
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
# Taiga
|
|
||||||
Open source Agile project management tool.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
Traffic flows: **Traefik → taiga-gateway (nginx) → internal services**
|
|
||||||
|
|
||||||
- `taiga-gateway` — nginx reverse proxy; the only service on the Traefik network
|
|
||||||
- `taiga-back` — Django backend API
|
|
||||||
- `taiga-async` — Celery worker (same image as back, different entrypoint)
|
|
||||||
- `taiga-front` — Angular frontend
|
|
||||||
- `taiga-events` — WebSocket events service
|
|
||||||
- `taiga-protected` — Protected media token verification
|
|
||||||
- `taiga-db` — PostgreSQL
|
|
||||||
- `taiga-async-rabbitmq` — RabbitMQ for Celery async tasks
|
|
||||||
- `taiga-events-rabbitmq` — RabbitMQ for real-time WebSocket events
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
1. Copy `.env.example` to `.env` and fill out all values. Use `openssl rand -base64 32` for key/password generation.
|
|
||||||
2. Start the stack: `docker compose up -d`
|
|
||||||
3. Create the admin user:
|
|
||||||
```
|
|
||||||
docker exec -it taiga-back python3 manage.py createsuperuser --username admin --email admin@example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
## Runbook
|
|
||||||
|
|
||||||
**Start / stop**
|
|
||||||
```
|
|
||||||
docker compose up -d
|
|
||||||
docker compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
**View logs**
|
|
||||||
```
|
|
||||||
docker compose logs -f # all services
|
|
||||||
docker compose logs -f taiga-back # one service
|
|
||||||
```
|
|
||||||
|
|
||||||
**Restart a single service**
|
|
||||||
```
|
|
||||||
docker compose restart taiga-back
|
|
||||||
```
|
|
||||||
|
|
||||||
**Create or reset admin user**
|
|
||||||
```
|
|
||||||
docker exec -it taiga-back python3 manage.py createsuperuser
|
|
||||||
```
|
|
||||||
|
|
||||||
**Update images**
|
|
||||||
```
|
|
||||||
docker compose pull
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
**Database backup / restore**
|
|
||||||
```
|
|
||||||
docker exec taiga-db pg_dump -U taiga_user taiga > backup.sql
|
|
||||||
docker exec -i taiga-db psql -U taiga_user taiga < backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
- Two separate RabbitMQ instances are required: one for Celery async tasks, one for WebSocket events.
|
|
||||||
- `EMAIL_USE_TLS` and `EMAIL_USE_SSL` are mutually exclusive — only set one to `True`.
|
|
||||||
- `EMAIL_BACKEND=console` logs emails to stdout (useful for testing). Switch to `smtp` for real email.
|
|
||||||
- Static files are stored in the `taiga-static` named Docker volume and shared between `taiga-back`, `taiga-async`, and `taiga-gateway`.
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
networks:
|
|
||||||
traefik:
|
|
||||||
external: true
|
|
||||||
taiga:
|
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
taiga-static:
|
|
||||||
taiga-async-rabbitmq-data:
|
|
||||||
taiga-events-rabbitmq-data:
|
|
||||||
|
|
||||||
# Shared environment for taiga-back and taiga-async (Celery worker)
|
|
||||||
x-environment:
|
|
||||||
&default-back-environment
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
POSTGRES_HOST: taiga-db
|
|
||||||
TAIGA_SECRET_KEY: ${SECRET_KEY}
|
|
||||||
TAIGA_SITES_SCHEME: ${TAIGA_SCHEME}
|
|
||||||
TAIGA_SITES_DOMAIN: ${DOMAIN}
|
|
||||||
TAIGA_SUBPATH: ${SUBPATH}
|
|
||||||
DEFAULT_FROM_EMAIL: ${EMAIL_HOST_USER}
|
|
||||||
EMAIL_BACKEND: "django.core.mail.backends.${EMAIL_BACKEND}.EmailBackend"
|
|
||||||
EMAIL_USE_TLS: ${EMAIL_USE_TLS}
|
|
||||||
EMAIL_USE_SSL: ${EMAIL_USE_SSL}
|
|
||||||
EMAIL_HOST: ${EMAIL_HOST}
|
|
||||||
EMAIL_PORT: ${EMAIL_PORT}
|
|
||||||
EMAIL_HOST_USER: ${EMAIL_HOST_USER}
|
|
||||||
EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD}
|
|
||||||
CELERY_ENABLED: "True"
|
|
||||||
RABBITMQ_USER: ${TAIGA_ASYNC_RABBITMQ_USER}
|
|
||||||
RABBITMQ_PASS: ${TAIGA_ASYNC_RABBITMQ_PASSWORD}
|
|
||||||
RABBITMQ_VHOST: ${TAIGA_ASYNC_RABBITMQ_VHOST}
|
|
||||||
RABBITMQ_HOST: taiga-async-rabbitmq
|
|
||||||
ENABLE_TELEMETRY: ${ENABLE_TELEMETRY}
|
|
||||||
|
|
||||||
services:
|
|
||||||
|
|
||||||
# ── Database ────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
taiga-db:
|
|
||||||
image: postgres:13
|
|
||||||
container_name: taiga-db
|
|
||||||
restart: always
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${POSTGRES_DB}
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
||||||
volumes:
|
|
||||||
- /pwspool/software/taiga/db:/var/lib/postgresql/data
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
# ── Backend ─────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
taiga-back:
|
|
||||||
image: taigaio/taiga-back:latest
|
|
||||||
container_name: taiga-back
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
<<: *default-back-environment
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
depends_on:
|
|
||||||
taiga-db:
|
|
||||||
condition: service_healthy
|
|
||||||
taiga-async-rabbitmq:
|
|
||||||
condition: service_healthy
|
|
||||||
volumes:
|
|
||||||
- taiga-static:/taiga-back/static-root
|
|
||||||
- /pwspool/software/taiga/back/media:/taiga-back/media
|
|
||||||
|
|
||||||
taiga-async:
|
|
||||||
image: taigaio/taiga-back:latest
|
|
||||||
container_name: taiga-async
|
|
||||||
entrypoint: ["/taiga-back/docker/async_entrypoint.sh"]
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
<<: *default-back-environment
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
depends_on:
|
|
||||||
taiga-db:
|
|
||||||
condition: service_healthy
|
|
||||||
taiga-async-rabbitmq:
|
|
||||||
condition: service_healthy
|
|
||||||
taiga-back:
|
|
||||||
condition: service_started
|
|
||||||
volumes:
|
|
||||||
- taiga-static:/taiga-back/static-root
|
|
||||||
- /pwspool/software/taiga/back/media:/taiga-back/media
|
|
||||||
|
|
||||||
# ── Async RabbitMQ (Celery tasks) ───────────────────────────────────────────
|
|
||||||
|
|
||||||
taiga-async-rabbitmq:
|
|
||||||
image: rabbitmq:3.8-management-alpine
|
|
||||||
container_name: taiga-async-rabbitmq
|
|
||||||
restart: always
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
environment:
|
|
||||||
RABBITMQ_DEFAULT_USER: ${TAIGA_ASYNC_RABBITMQ_USER}
|
|
||||||
RABBITMQ_DEFAULT_PASS: ${TAIGA_ASYNC_RABBITMQ_PASSWORD}
|
|
||||||
RABBITMQ_DEFAULT_VHOST: ${TAIGA_ASYNC_RABBITMQ_VHOST}
|
|
||||||
RABBITMQ_ERLANG_COOKIE: ${RABBITMQ_ERLANG_COOKIE}
|
|
||||||
volumes:
|
|
||||||
- taiga-async-rabbitmq-data:/var/lib/rabbitmq
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "rabbitmq-diagnostics", "check_running", "-q"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
# ── Frontend ─────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
taiga-front:
|
|
||||||
image: taigaio/taiga-front:latest
|
|
||||||
container_name: taiga-front
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
TAIGA_URL: "${TAIGA_SCHEME}://${DOMAIN}"
|
|
||||||
TAIGA_WEBSOCKETS_URL: "${WEBSOCKETS_SCHEME}://${DOMAIN}"
|
|
||||||
TAIGA_SUBPATH: "${SUBPATH}"
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
depends_on:
|
|
||||||
- taiga-back
|
|
||||||
|
|
||||||
# ── Events (WebSocket) ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
taiga-events:
|
|
||||||
image: taigaio/taiga-events:latest
|
|
||||||
container_name: taiga-events
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
RABBITMQ_URL: "amqp://${TAIGA_EVENTS_RABBITMQ_USER}:${TAIGA_EVENTS_RABBITMQ_PASSWORD}@taiga-events-rabbitmq:5672/${TAIGA_EVENTS_RABBITMQ_VHOST}"
|
|
||||||
TAIGA_SECRET_KEY: ${SECRET_KEY}
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
depends_on:
|
|
||||||
taiga-events-rabbitmq:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
taiga-events-rabbitmq:
|
|
||||||
image: rabbitmq:3.8-management-alpine
|
|
||||||
container_name: taiga-events-rabbitmq
|
|
||||||
restart: always
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
environment:
|
|
||||||
RABBITMQ_DEFAULT_USER: ${TAIGA_EVENTS_RABBITMQ_USER}
|
|
||||||
RABBITMQ_DEFAULT_PASS: ${TAIGA_EVENTS_RABBITMQ_PASSWORD}
|
|
||||||
RABBITMQ_DEFAULT_VHOST: ${TAIGA_EVENTS_RABBITMQ_VHOST}
|
|
||||||
RABBITMQ_ERLANG_COOKIE: ${RABBITMQ_ERLANG_COOKIE}
|
|
||||||
volumes:
|
|
||||||
- taiga-events-rabbitmq-data:/var/lib/rabbitmq
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "rabbitmq-diagnostics", "check_running", "-q"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
# ── Protected media service ──────────────────────────────────────────────────
|
|
||||||
|
|
||||||
taiga-protected:
|
|
||||||
image: taigaio/taiga-protected:latest
|
|
||||||
container_name: taiga-protected
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
SECRET_KEY: ${SECRET_KEY}
|
|
||||||
MAX_AGE: 360
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
volumes:
|
|
||||||
- /pwspool/software/taiga/back/media:/taiga-back/media
|
|
||||||
|
|
||||||
# ── Gateway (nginx, the only service exposed to Traefik) ────────────────────
|
|
||||||
|
|
||||||
taiga-gateway:
|
|
||||||
image: nginx:alpine
|
|
||||||
container_name: taiga-gateway
|
|
||||||
restart: always
|
|
||||||
networks:
|
|
||||||
- taiga
|
|
||||||
- traefik
|
|
||||||
depends_on:
|
|
||||||
- taiga-front
|
|
||||||
- taiga-back
|
|
||||||
- taiga-events
|
|
||||||
- taiga-protected
|
|
||||||
volumes:
|
|
||||||
- taiga-static:/taiga/static:ro
|
|
||||||
- /pwspool/software/taiga/back/media:/taiga/media:ro
|
|
||||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
|
||||||
labels:
|
|
||||||
- traefik.enable=true
|
|
||||||
- traefik.http.routers.taiga.rule=Host(`${DOMAIN}`)
|
|
||||||
- traefik.http.routers.taiga.tls=true
|
|
||||||
- traefik.http.routers.taiga.tls.certresolver=lets-encrypt
|
|
||||||
- traefik.http.services.taiga.loadbalancer.server.port=80
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80 default_server;
|
|
||||||
|
|
||||||
client_max_body_size 100M;
|
|
||||||
charset utf-8;
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
location / {
|
|
||||||
proxy_pass http://taiga-front/;
|
|
||||||
proxy_pass_header Server;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Scheme $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
}
|
|
||||||
|
|
||||||
# API
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://taiga-back:8000/api/;
|
|
||||||
proxy_pass_header Server;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Scheme $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Admin
|
|
||||||
location /admin/ {
|
|
||||||
proxy_pass http://taiga-back:8000/admin/;
|
|
||||||
proxy_pass_header Server;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_redirect off;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Scheme $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Events (WebSocket)
|
|
||||||
location /events {
|
|
||||||
proxy_pass http://taiga-events:8888/events;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_connect_timeout 7d;
|
|
||||||
proxy_send_timeout 7d;
|
|
||||||
proxy_read_timeout 7d;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Protected media (internal redirect target)
|
|
||||||
location /_protected {
|
|
||||||
internal;
|
|
||||||
alias /taiga/media;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Public exports
|
|
||||||
location /media/exports/ {
|
|
||||||
alias /taiga/media/exports/;
|
|
||||||
add_header Content-Disposition 'attachment';
|
|
||||||
}
|
|
||||||
|
|
||||||
# Media (proxied through taiga-protected for token verification)
|
|
||||||
location /media/ {
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Scheme $scheme;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_pass http://taiga-protected:8003/;
|
|
||||||
proxy_redirect off;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Static assets
|
|
||||||
location /static/ {
|
|
||||||
alias /taiga/static/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
productivity/taiga/.env.example
Normal file
21
productivity/taiga/.env.example
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
DOMAIN=taiga.example.com
|
||||||
|
TAIGA_SECRET_KEY=change-this-to-a-secure-random-string
|
||||||
|
TAIGA_ADMIN_USERNAME=admin
|
||||||
|
TAIGA_ADMIN_EMAIL=admin@example.com
|
||||||
|
TAIGA_ADMIN_PASSWORD=admin
|
||||||
|
|
||||||
|
# Postgres
|
||||||
|
POSTGRES_HOST=postgres
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_USER=taiga_user
|
||||||
|
POSTGRES_PASSWORD=securepassword
|
||||||
|
POSTGRES_DB=taiga
|
||||||
|
|
||||||
|
# RabbitMQ
|
||||||
|
RABBITMQ_HOST=rabbitmq
|
||||||
|
RABBITMQ_USER=taiga_user
|
||||||
|
RABBITMQ_PASSWORD=securepassword
|
||||||
|
|
||||||
|
CELERY_ENABLED=False
|
||||||
|
CELERY_BROKER=amqp://${RABBITMQ_USER}:${RABBITMQ_PASSWORD}@${RABBITMQ_HOST}:5672/
|
||||||
|
CELERY_ALWAYS_EAGER=False
|
||||||
12
productivity/taiga/README.md
Normal file
12
productivity/taiga/README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Taiga
|
||||||
|
Open source Agile project management tool.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
1. Fill out `.env` from the example. You are supposed to be able to set admin credentials here but I had issues with that. Use a util like `openssl` for key generation.
|
||||||
|
2. Setup credentials using python util:
|
||||||
|
```
|
||||||
|
docker exec -it taiga-back python3 manage.py createsuperuser --username admin --email admin@example.com
|
||||||
|
```
|
||||||
|
```
|
||||||
|
```
|
||||||
|
Note: I had issues doing this call while RabbitMQ was enabled via this Celery util. I set `CELERY_ENABLED=false` to get the call to work. This is supposed to have us use some slower utility as an alternative. AI assured me I could re-enable after the fact but I did not just to be safe.
|
||||||
80
productivity/taiga/docker-compose.yml
Normal file
80
productivity/taiga/docker-compose.yml
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
networks:
|
||||||
|
traefik:
|
||||||
|
external: true
|
||||||
|
taiga:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
services:
|
||||||
|
taiga-front:
|
||||||
|
image: taigaio/taiga-front:latest
|
||||||
|
container_name: taiga-front
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- TAIGA_API_URL=https://${DOMAIN}/api/v1
|
||||||
|
- TAIGA_EVENTS_URL=wss://${DOMAIN}/events
|
||||||
|
networks:
|
||||||
|
- taiga
|
||||||
|
- traefik
|
||||||
|
depends_on:
|
||||||
|
- taiga-back
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.taiga.rule=Host(`${DOMAIN}`)
|
||||||
|
- traefik.http.routers.taiga.tls=true
|
||||||
|
- traefik.http.routers.taiga.tls.certresolver=lets-encrypt
|
||||||
|
- traefik.http.services.taiga.loadbalancer.server.port=80
|
||||||
|
|
||||||
|
taiga-back:
|
||||||
|
image: taigaio/taiga-back:latest
|
||||||
|
container_name: taiga-back
|
||||||
|
restart: always
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
volumes:
|
||||||
|
- /pwspool/software/taiga/back/data:/taiga-back/data
|
||||||
|
- /pwspool/software/taiga/back/media:/taiga-back/media
|
||||||
|
networks:
|
||||||
|
- taiga
|
||||||
|
- traefik
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- rabbitmq
|
||||||
|
- taiga-events
|
||||||
|
labels:
|
||||||
|
- traefik.http.routers.taiga-back.rule=Host(`${DOMAIN}`) && PathPrefix(`/api`)
|
||||||
|
- traefik.http.routers.taiga-back.tls=true
|
||||||
|
- traefik.http.routers.taiga-back.tls.certresolver=lets-encrypt
|
||||||
|
- traefik.http.services.taiga-back.loadbalancer.server.port=8000
|
||||||
|
|
||||||
|
taiga-events:
|
||||||
|
image: taigaio/taiga-events:latest
|
||||||
|
container_name: taiga-events
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- TAIGA_SECRET=${TAIGA_SECRET_KEY}
|
||||||
|
- TAIGA_BACK_HOST=http://taiga-back:8000
|
||||||
|
networks:
|
||||||
|
- taiga
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:13
|
||||||
|
container_name: taiga-db
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- taiga
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- /pwspool/software/taiga/db:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq:3-management
|
||||||
|
container_name: taiga-rabbitmq
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- taiga
|
||||||
|
environment:
|
||||||
|
- RABBITMQ_DEFAULT_USER=${RABBITMQ_USER}
|
||||||
|
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD}
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user