Project Management intermediate ⏱ 45 minutes

Plane: Open-Source Jira Alternative

Modern project planning and tracking. Issues, sprints, and roadmaps with beautiful UI.

By Homelab Team • 2025-01-15
#plane#project-management#jira#issues#docker

Introduction

Plane is a modern, open-source project management platform designed to rival Jira and Linear. Built for agile teams, Plane offers a beautiful interface with powerful features including kanban boards, sprint planning, issue tracking, roadmaps, and analytics. With over 25,000 GitHub stars and backing from Y Combinator, Plane has become the leading self-hosted alternative to expensive SaaS project management tools.

Why Choose Plane?

Modern User Experience

Unlike Jira’s complex and cluttered interface, Plane provides a clean, intuitive design that teams can adopt immediately without extensive training. The interface is fast, responsive, and built with modern web technologies.

Complete Feature Parity

Plane includes all the features you’d expect from enterprise project management tools: multiple projects, customizable workflows, sprints, backlogs, roadmaps, time tracking, and detailed reporting.

Privacy and Data Control

Your project data, roadmaps, and strategic planning stay on your infrastructure. No third-party vendor has access to your intellectual property or business plans.

Cost Savings

Jira costs $7.75-15.25/user/month. For a team of 25, that’s $2,325-4,575 annually. Plane is completely free with unlimited users, projects, and features.

Developer-Friendly

Built by developers for developers, Plane integrates seamlessly with GitHub, GitLab, provides a powerful API, supports webhooks, and offers CLI tools for automation.

Key Features:

  • Multiple workspaces and projects
  • Kanban, list, and calendar views
  • Sprint planning and backlog management
  • Issue tracking with custom fields
  • Roadmap planning and timeline views
  • Cycle management for iterations
  • Module-based organization
  • Pages for documentation
  • Detailed analytics and reports
  • GitHub/GitLab integration
  • API and webhooks
  • Custom workflows and automation
  • Time tracking and estimates
  • Labels, priorities, and assignees
  • Rich text and Markdown support
  • File attachments and comments

Understanding the Architecture

Plane consists of multiple microservices working together:

Internet/Local Network
        │
        ▼
┌───────────────┐
│ Reverse Proxy │ (Nginx, Traefik, Caddy)
│  HTTPS/SSL    │
└───────┬───────┘
        │
        ▼
┌───────────────┐
│   Plane Web   │ (React Frontend - Port 3000)
└───────┬───────┘
        │
    ┌───┴────┬──────────┬─────────┐
    │        │          │         │
    ▼        ▼          ▼         ▼
┌──────┐ ┌──────┐ ┌────────┐ ┌──────┐
│ API  │ │Space │ │ Admin  │ │Live  │
│:8000 │ │:4000 │ │ :8000  │ │:3000 │
└──┬───┘ └──────┘ └────────┘ └──────┘
   │
   ├─────┬──────────┬─────────┐
   │     │          │         │
   ▼     ▼          ▼         ▼
┌────┐ ┌────┐ ┌────────┐ ┌──────┐
│Post│ │Redis│ │ MinIO  │ │Worker│
│gres│ │Cache│ │Storage │ │Queue │
└────┘ └────┘ └────────┘ └──────┘

Prerequisites

Hardware Requirements

Minimum Configuration:

  • RAM: 4GB (handles small teams up to 10 users)
  • CPU: 2 cores
  • Storage: 20GB SSD
  • Network: Standard homelab network

Recommended Configuration:

  • RAM: 8GB (better for 25+ users)
  • CPU: 4 cores
  • Storage: 50GB+ SSD
  • Network: Gigabit ethernet

Production Environment (50+ users):

  • RAM: 16GB+
  • CPU: 8+ cores
  • Storage: 100GB+ SSD with fast I/O
  • Dedicated database server

Software Requirements

  • Docker Engine v24+ with docker-compose
  • Linux (Ubuntu 22.04/24.04 LTS recommended)
  • 2GB+ available RAM
  • Domain name (optional, for HTTPS)

Pre-Deployment Checklist

# Verify Docker installation
docker --version
docker compose version

# Check system resources
free -h
df -h

# Verify network connectivity
ping -c 3 google.com

Installation and Deployment

Step 1: Create Project Structure

# Create directory structure
mkdir -p ~/plane/{data,env}
cd ~/plane

# Set proper permissions
chmod 755 ~/plane

Step 2: Generate Secure Secrets

# Generate required secrets
export SECRET_KEY=$(openssl rand -hex 32)
export POSTGRES_PASSWORD=$(openssl rand -base64 24)
export REDIS_PASSWORD=$(openssl rand -base64 24)
export MINIO_ACCESS_KEY=$(openssl rand -hex 16)
export MINIO_SECRET_KEY=$(openssl rand -hex 32)

echo "Save these credentials securely:"
echo "SECRET_KEY: $SECRET_KEY"
echo "POSTGRES_PASSWORD: $POSTGRES_PASSWORD"
echo "REDIS_PASSWORD: $REDIS_PASSWORD"
echo "MINIO_ACCESS_KEY: $MINIO_ACCESS_KEY"
echo "MINIO_SECRET_KEY: $MINIO_SECRET_KEY"

Step 3: Create Environment Configuration

Create .env file:

cat > .env <<EOF
# Application Configuration
WEB_URL=http://YOUR-SERVER-IP:3000
CORS_ALLOWED_ORIGINS=http://YOUR-SERVER-IP:3000

# Database Configuration
POSTGRES_USER=plane
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_DB=plane
DATABASE_URL=postgresql://plane:${POSTGRES_PASSWORD}@plane-db:5432/plane

# Redis Configuration
REDIS_URL=redis://plane-redis:6379/
REDIS_PASSWORD=${REDIS_PASSWORD}

# MinIO Configuration (File Storage)
USE_MINIO=1
MINIO_ROOT_USER=${MINIO_ACCESS_KEY}
MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY}
MINIO_ENDPOINT=plane-minio:9000
AWS_ACCESS_KEY_ID=${MINIO_ACCESS_KEY}
AWS_SECRET_ACCESS_KEY=${MINIO_SECRET_KEY}
AWS_REGION=us-east-1
AWS_S3_BUCKET_NAME=plane-uploads

# Security
SECRET_KEY=${SECRET_KEY}

# Email Configuration (Optional - for notifications)
# EMAIL_HOST=smtp.gmail.com
# EMAIL_PORT=587
# EMAIL_USE_TLS=1
# [email protected]
# EMAIL_HOST_PASSWORD=your-app-password
# [email protected]

# GitHub Integration (Optional)
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=

# Enable/Disable Features
ENABLE_SIGNUP=1
ENABLE_EMAIL_PASSWORD=1
ENABLE_MAGIC_LINK_LOGIN=0
EOF

# Secure the file
chmod 600 .env

Important: Replace YOUR-SERVER-IP with your server’s actual IP address.

Step 4: Create Docker Compose Configuration

Create docker-compose.yml:

version: "3.8"

x-app-env: &app-env
  environment:
    - WEB_URL=${WEB_URL}
    - DATABASE_URL=${DATABASE_URL}
    - REDIS_URL=${REDIS_URL}
    - SECRET_KEY=${SECRET_KEY}
    - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
    - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
    - AWS_REGION=${AWS_REGION}
    - AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME}
    - MINIO_ENDPOINT=${MINIO_ENDPOINT}
    - USE_MINIO=${USE_MINIO}
    - CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS}

services:
  # PostgreSQL Database
  plane-db:
    image: postgres:15-alpine
    container_name: plane-db
    restart: unless-stopped
    command: postgres -c 'max_connections=1000'
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
      PGDATA: /var/lib/postgresql/data
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - plane-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U plane"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis Cache
  plane-redis:
    image: redis:7-alpine
    container_name: plane-redis
    restart: unless-stopped
    volumes:
      - redis-data:/data
    networks:
      - plane-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # MinIO Object Storage
  plane-minio:
    image: minio/minio:latest
    container_name: plane-minio
    restart: unless-stopped
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
    volumes:
      - minio-data:/data
    networks:
      - plane-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  # Plane API Backend
  plane-api:
    image: makeplane/plane-backend:latest
    container_name: plane-api
    restart: unless-stopped
    <<: *app-env
    depends_on:
      plane-db:
        condition: service_healthy
      plane-redis:
        condition: service_healthy
      plane-minio:
        condition: service_healthy
    command: ./bin/docker-entrypoint-api.sh
    volumes:
      - logs-api:/code/plane/logs
    networks:
      - plane-network

  # Plane Worker (Background Jobs)
  plane-worker:
    image: makeplane/plane-backend:latest
    container_name: plane-worker
    restart: unless-stopped
    <<: *app-env
    depends_on:
      - plane-api
    command: ./bin/docker-entrypoint-worker.sh
    volumes:
      - logs-worker:/code/plane/logs
    networks:
      - plane-network

  # Plane Beat Scheduler
  plane-beat-worker:
    image: makeplane/plane-backend:latest
    container_name: plane-beat-worker
    restart: unless-stopped
    <<: *app-env
    depends_on:
      - plane-api
    command: ./bin/docker-entrypoint-beat.sh
    volumes:
      - logs-beat:/code/plane/logs
    networks:
      - plane-network

  # Plane Web Frontend
  plane-web:
    image: makeplane/plane-frontend:latest
    container_name: plane-web
    restart: unless-stopped
    environment:
      - NEXT_PUBLIC_API_BASE_URL=http://plane-api:8000
    depends_on:
      - plane-api
    ports:
      - "3000:3000"
    networks:
      - plane-network

  # Plane Space (Public pages)
  plane-space:
    image: makeplane/plane-space:latest
    container_name: plane-space
    restart: unless-stopped
    environment:
      - NEXT_PUBLIC_API_BASE_URL=http://plane-api:8000
    depends_on:
      - plane-api
    ports:
      - "4000:3000"
    networks:
      - plane-network

  # Plane Admin
  plane-admin:
    image: makeplane/plane-admin:latest
    container_name: plane-admin
    restart: unless-stopped
    environment:
      - NEXT_PUBLIC_API_BASE_URL=http://plane-api:8000
    depends_on:
      - plane-api
    ports:
      - "8000:3000"
    networks:
      - plane-network

volumes:
  postgres-data:
  redis-data:
  minio-data:
  logs-api:
  logs-worker:
  logs-beat:

networks:
  plane-network:
    driver: bridge

Step 5: Deploy Plane

# Pull all images
docker compose pull

# Start services
docker compose up -d

# Monitor startup logs
docker compose logs -f

# Wait for all services to be healthy (2-3 minutes)
# Press Ctrl+C when you see "Application startup complete"

# Verify all containers are running
docker compose ps

All containers should show “Up” or “healthy” status.

Step 6: Initialize MinIO Bucket

# Create MinIO bucket for uploads
docker exec plane-minio mc alias set minio http://localhost:9000 ${MINIO_ROOT_USER} ${MINIO_ROOT_PASSWORD}
docker exec plane-minio mc mb minio/plane-uploads
docker exec plane-minio mc anonymous set download minio/plane-uploads

Step 7: Access Plane

Navigate to http://YOUR-SERVER-IP:3000 in your browser.

Create your admin account:

  1. Click “Sign Up”
  2. Enter your email and password
  3. Set your full name
  4. Click “Create Account”
  5. You’ll be logged in automatically

Step 8: Create Your First Workspace

1. Click "Create Workspace"
2. Workspace Name: "My Company"
3. Workspace URL: "my-company" (unique identifier)
4. Click "Create"
5. You're now in your workspace!

Initial Setup and Configuration

Create Your First Project

1. Click "Create Project" in sidebar
2. Project Details:
   - Name: "Website Redesign"
   - Identifier: "WEB" (appears on issues: WEB-1, WEB-2)
   - Description: "Redesign company website"
   - Network: Private (only team members)
3. Click "Create Project"

Configure Project Settings

Navigate to Project Settings:

General Settings:
├─ Project Lead: Assign yourself
├─ Start Date: Set project start
├─ Target Date: Set deadline
└─ Cover Image: Upload project image

Features:
├─ Enable Cycles: Yes (for sprints)
├─ Enable Modules: Yes (for feature grouping)
├─ Enable Pages: Yes (for documentation)
└─ Enable Inbox: Yes (for issue triage)

States:
├─ Backlog (default)
├─ Todo
├─ In Progress
├─ Done
└─ Cancelled
(Customize as needed)

Set Up Custom Labels

1. Project Settings → Labels
2. Create labels:
   - bug (red)
   - feature (blue)
   - enhancement (green)
   - documentation (yellow)
   - high-priority (orange)
3. Save

Configure Issue Priorities

Default priorities are:

  • Urgent
  • High
  • Medium
  • Low
  • None

Customize in Project Settings → Priorities if needed.

User Management

Invite Team Members

1. Workspace Settings → Members
2. Click "Invite Members"
3. Enter email addresses (comma-separated)
4. Select role:
   - Admin: Full workspace control
   - Member: Can create projects
   - Viewer: Read-only access
   - Guest: Limited project access
5. Click "Send Invites"

Project-Specific Roles

Add members to specific projects:

1. Open project
2. Project Settings → Members
3. Click "Add Members"
4. Select workspace members
5. Assign role:
   - Admin: Full project control
   - Member: Create and edit issues
   - Viewer: Read-only
   - Guest: Specific issue access
6. Save

Create Teams

Organize members into teams:

1. Workspace Settings → Teams
2. Click "Create Team"
3. Team Name: "Backend Team"
4. Add members
5. Assign to projects
6. Save

Working with Issues

Create Issues

1. Navigate to project
2. Click "New Issue" or press 'C'
3. Fill details:
   - Title: "Add user authentication"
   - Description: Use rich text or Markdown
   - Assignee: Select team member
   - Priority: High
   - Labels: feature, backend
   - Estimate: 5 (story points or hours)
   - Start/Due dates
4. Click "Create Issue"

Issue Views

Kanban View:

Drag and drop issues between columns (states)
Group by: Assignee, Priority, Labels, State
Perfect for visual workflow management

List View:

Spreadsheet-like view
Bulk edit multiple issues
Filter and sort efficiently

Calendar View:

See issues by due dates
Visualize deadlines
Plan sprint capacity

Gantt View:

Timeline visualization
See dependencies
Track project progress

Use Cycles (Sprints)

1. Click "Cycles" in sidebar
2. Click "Create Cycle"
3. Configure:
   - Name: "Sprint 1"
   - Start Date: 2025-01-20
   - End Date: 2025-02-03
   - Description: Sprint goals
4. Add issues to cycle:
   - Drag from backlog
   - Or assign in issue details
5. Monitor cycle progress:
   - Burndown chart
   - Velocity tracking
   - Completion percentage

Use Modules (Features/Epics)

1. Click "Modules" in sidebar
2. Create module:
   - Name: "User Authentication"
   - Description: "Complete auth system"
   - Lead: Assign owner
   - Start/Target dates
3. Add issues to module
4. Track progress
5. Link multiple cycles to one module

Pages (Documentation)

1. Click "Pages" in sidebar
2. Create page:
   - Title: "API Documentation"
   - Content: Rich text or Markdown
   - Add code blocks
   - Embed images
   - Link to issues
3. Organize in folders
4. Share with team

Backup Procedures

What to Backup

ComponentPriorityContains
PostgreSQLCriticalAll data: issues, projects, users
MinIOImportantFile attachments, images
.env fileCriticalConfiguration and secrets
RedisOptionalCache only, can rebuild

Automated Backup Script

Create ~/plane/backup.sh:

#!/bin/bash

# Configuration
BACKUP_DIR="/mnt/backups/plane"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=30

# Load environment
source ~/plane/.env

# Create backup directory
mkdir -p "$BACKUP_DIR"

echo "Starting Plane backup at $(date)"

# Backup PostgreSQL database
docker exec plane-db pg_dump -U plane plane | gzip > "$BACKUP_DIR/plane-db-${DATE}.sql.gz"

if [ $? -eq 0 ]; then
    echo "Database backed up successfully"
else
    echo "Database backup FAILED" >&2
    exit 1
fi

# Backup MinIO data
docker run --rm   -v plane_minio-data:/source:ro   -v "$BACKUP_DIR":/backup   alpine tar czf /backup/minio-data-${DATE}.tar.gz -C /source .

echo "MinIO data backed up"

# Backup environment file
cp ~/plane/.env "$BACKUP_DIR/env-${DATE}.txt"

# Create backup manifest
cat > "$BACKUP_DIR/manifest-${DATE}.txt" <<EOF
Plane Backup
Date: $(date)
Database: plane-db-${DATE}.sql.gz
MinIO: minio-data-${DATE}.tar.gz
Config: env-${DATE}.txt
EOF

# Clean old backups
find "$BACKUP_DIR" -name "*.gz" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR" -name "*.txt" -mtime +$RETENTION_DAYS -delete

echo "Backup completed at $(date)"
echo "Location: $BACKUP_DIR"

Schedule daily backups:

chmod +x ~/plane/backup.sh

# Add to crontab
crontab -e
# Add: 0 2 * * * /home/user/plane/backup.sh >> /var/log/plane-backup.log 2>&1

Restore from Backup

# Stop Plane
cd ~/plane
docker compose down

# Restore database
gunzip < /mnt/backups/plane/plane-db-YYYYMMDD.sql.gz |   docker exec -i plane-db psql -U plane plane

# Restore MinIO data
docker run --rm   -v plane_minio-data:/dest   -v /mnt/backups/plane:/backup   alpine tar xzf /backup/minio-data-YYYYMMDD.tar.gz -C /dest

# Restore environment file
cp /mnt/backups/plane/env-YYYYMMDD.txt .env

# Start Plane
docker compose up -d

# Verify
docker compose logs -f

Troubleshooting

Issue: Services Won’t Start

# Check logs for specific service
docker compose logs plane-api
docker compose logs plane-db

# Common fixes:
# 1. Database not ready
docker compose restart plane-db
sleep 30
docker compose restart plane-api

# 2. Port conflicts
sudo netstat -tlnp | grep :3000
# Change port in docker-compose.yml if needed

# 3. Permission issues
sudo chown -R $(id -u):$(id -g) ~/plane/data

Issue: Can’t Upload Files

# Verify MinIO is running
docker compose ps plane-minio

# Check MinIO bucket
docker exec plane-minio mc ls minio/

# Recreate bucket if missing
docker exec plane-minio mc mb minio/plane-uploads
docker exec plane-minio mc anonymous set download minio/plane-uploads

# Restart API
docker compose restart plane-api

Issue: Slow Performance

# Check resource usage
docker stats

# Increase PostgreSQL connections
# Edit docker-compose.yml:
# command: postgres -c 'max_connections=2000'

# Clear Redis cache
docker exec plane-redis redis-cli FLUSHDB

# Restart all services
docker compose restart

Issue: Email Not Sending

# Test SMTP settings
docker exec plane-api python manage.py shell

# In Python shell:
from django.core.mail import send_mail
send_mail(
    'Test Subject',
    'Test message.',
    '[email protected]',
    ['[email protected]'],
    fail_silently=False,
)

# Check logs
docker compose logs plane-api | grep -i mail

Issue: GitHub Integration Not Working

# Verify GitHub OAuth app settings:
# Homepage URL: http://YOUR-SERVER-IP:3000
# Callback URL: http://YOUR-SERVER-IP:3000/auth/github/callback

# Update .env with correct credentials
# Restart services
docker compose restart

Advanced Configuration

Configure HTTPS with Reverse Proxy

Using Caddy:

Create Caddyfile:

plane.yourdomain.com {
    reverse_proxy plane-web:3000
    encode gzip
}

plane-api.yourdomain.com {
    reverse_proxy plane-api:8000
    encode gzip
}

Update .env:

WEB_URL=https://plane.yourdomain.com
CORS_ALLOWED_ORIGINS=https://plane.yourdomain.com

Enable GitHub Integration

# Create GitHub OAuth App:
# https://github.com/settings/developers
# Homepage: https://plane.yourdomain.com
# Callback: https://plane.yourdomain.com/auth/github/callback

# Add to .env:
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret

# Restart
docker compose restart

Configure Webhooks

1. Project Settings → Webhooks
2. Click "Create Webhook"
3. URL: https://your-webhook-endpoint.com
4. Events to trigger:
   - Issue created
   - Issue updated
   - Issue deleted
   - Comment added
5. Save
6. Test webhook

Conclusion

Plane provides enterprise-grade project management capabilities without the enterprise cost. With its modern interface, powerful features, and complete self-hosting, it’s the perfect solution for teams that want control over their project data.

What You’ve Accomplished:

  • Deployed a full Plane instance with all microservices
  • Configured PostgreSQL, Redis, and MinIO
  • Set up workspaces and projects
  • Implemented automated backups
  • Configured user management
  • Enabled advanced features

Next Steps:

  1. Import existing projects from Jira or Linear
  2. Set up GitHub/GitLab integration
  3. Configure custom workflows
  4. Create automation rules
  5. Set up analytics dashboards
  6. Train your team on Plane features

Start managing your projects with the power and flexibility that only self-hosting provides!