Skip to content

Repository files navigation

Pliny

License: ELv2Docker

Self-hosted kanban for teams who own their data. Built with React, TypeScript, Express, and PostgreSQL.

getpliny.com · Self-hosting guide


Features

  • Full kanban board with drag-and-drop (desktop & mobile touch)
  • Multiple views — board, calendar, list, and analytics dashboard
  • Labels, assignees, due dates, checklists, and card descriptions
  • Board starring and sort preferences
  • Public board sharing — shareable read-only links
  • CSV import — create boards from a spreadsheet in 3 steps
  • User management — ADMIN and READ roles with board-level permissions
  • SSO / OIDC — plug in any OIDC-compatible identity provider
  • REST API with personal access tokens
  • Real-time updates via WebSocket
  • Mobile-responsive with touch-friendly drag handles
  • Docker-ready — pulls from GHCR, no build step required

Quick Start

Prerequisites

  • Docker 20+
  • Docker Compose v2+

Deploy

git clone https://github.com/bshandley/pliny
cd pliny
cp .env.example .env
# Edit .env — set DB_PASSWORD, JWT_SECRET, PLINY_URL, and initial admin credentials
docker compose up -d

Open http://localhost:8080 (or your PLINY_URL). Create your admin account on first launch.

Images are pulled automatically from GitHub Container Registry. Migrations run automatically on startup — no extra steps.

Updating

docker compose pull && docker compose up -d

Migrations run automatically on restart. That's it.


Environment Variables

VariableRequiredDefaultDescription
DB_PASSWORDPostgreSQL password
JWT_SECRETSecret for JWT signing — use a long random string
PLINY_URLPublic URL of your instance (e.g. https://pliny.example.com)
DB_HOSTdbPostgreSQL host
DB_PORT5432PostgreSQL port
DB_NAMEplinyDatabase name
DB_USERplinyDatabase user
PORT3001Backend API port
INITIAL_ADMIN_USERNAMEadminUsername for the initial admin account (only used when no users exist)
INITIAL_ADMIN_PASSWORDPassword for the initial admin account
SMTP_HOSTSMTP host for email notifications
SMTP_PORT587SMTP port
SMTP_USERSMTP username
SMTP_PASSSMTP password
SMTP_FROMFrom address for outbound email
S3_ENDPOINTS3-compatible storage endpoint (uses local disk by default)
S3_BUCKETS3 bucket name
S3_ACCESS_KEYS3 access key
S3_SECRET_KEYS3 secret key
OIDC_ISSUEROIDC provider URL for SSO
OIDC_CLIENT_IDOIDC client ID
OIDC_CLIENT_SECRETOIDC client secret
TOTP_ENCRYPTION_KEYEncryption key for TOTP secrets

Reverse Proxy

Point your reverse proxy to port 8080 (the client container). WebSocket support is required — forward the Upgrade header.

nginx example:

location / {proxy_passhttp://localhost:8080;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_set_header Host $host;}

Works with nginx, Caddy, Traefik, or any WebSocket-capable proxy.


Architecture

pliny/
├── server/ # Express + TypeScript backend
│ ├── src/
│ │ ├── routes/ # API routes
│ │ ├── middleware/ # Auth & RBAC
│ │ ├── migrations/ # Database schema (idempotent)
│ │ └── index.ts # Server entry + WebSocket
│ └── Dockerfile
├── client/ # React + TypeScript frontend
│ ├── src/
│ │ ├── components/ # UI components
│ │ ├── api.ts # API client
│ │ └── App.tsx # App root
│ ├── Dockerfile
│ └── nginx.conf
└── docker-compose.yml

API

All endpoints require a Bearer token (JWT from login, or a personal access token).

Authentication

MethodPathDescription
POST/api/auth/loginLogin — returns JWT
POST/api/auth/registerRegister new user (ADMIN only)

Boards

MethodPathDescription
GET/api/boardsList boards
POST/api/boardsCreate board (ADMIN)
GET/api/boards/:idGet board with columns & cards
PUT/api/boards/:idUpdate board (ADMIN)
DELETE/api/boards/:idDelete board (ADMIN)
PUT/api/boards/:id/starStar/unstar board
GET/api/boards/:id/analyticsBoard analytics (activity, assignees)

Board Members (ADMIN only)

MethodPathDescription
GET/api/boards/:id/membersList members
POST/api/boards/:id/membersAdd member
DELETE/api/boards/:id/members/:userIdRemove member

Columns

MethodPathDescription
POST/api/columnsCreate column (ADMIN)
PUT/api/columns/:idUpdate column (ADMIN)
DELETE/api/columns/:idDelete column (ADMIN)

Cards

MethodPathDescription
POST/api/cardsCreate card (ADMIN)
GET/api/cards/:idGet card detail
PUT/api/cards/:idUpdate card (ADMIN)
DELETE/api/cards/:idDelete card (ADMIN)

Import

MethodPathDescription
POST/api/csv/board-import/previewPreview CSV import
POST/api/csv/board-import/confirmConfirm and create board from CSV

Admin

MethodPathDescription
GET/api/usersList users (ADMIN)
PUT/api/users/:idUpdate user (ADMIN)
DELETE/api/users/:idDelete user (ADMIN)
GET/api/boards/admin/sharedList all publicly shared boards (ADMIN)

Permissions

  • ADMIN — Full access. Manages users, boards, members, columns, and cards.
  • READ — View-only. Sees only boards they've been added to. Cannot modify anything.

Local Development

Backend:

cd server
npm install
npm run dev # starts with ts-node-dev
npm run migrate # run migrations against local DB

Frontend:

cd client
npm install
npm run dev # Vite dev server on :5173

Database:

docker run -d -p 5432:5432 \
-e POSTGRES_DB=pliny \
-e POSTGRES_USER=pliny \
-e POSTGRES_PASSWORD=changeme \
postgres:16

License

Elastic License 2.0 — free to self-host and modify; commercial redistribution and managed service offerings require a separate agreement.

About

Self-hosted kanban board

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages