Home Plane.so Docker Self-Hosted Setup Record
Post
Cancel

Plane.so Docker Self-Hosted Setup Record

Plane.so Docker Self-Hosted Setup Record

Plane Self-Hosted Docker setup, backup, restore, Nginx Domain reverse proxy configuration tutorial

Introduction

Plane.so is a free open-source project management tool similar to Asana, Jira, Clickup that supports Self-Hosted setup. It was established in 2022, with the first version released in 2023, and is still under development.

For detailed usage and development process integration, please refer to the previous article “Plane.so Free Open-Source Project Management Tool Similar to Asana/Jira that Supports Self-Hosted”. This article only records the process of setting up Plane.so using Docker.

Self-Hosted Plane

Docker Compose - Plane In this guide, we will walk you through the process of setting up a self-hosted environment. Self-hosting allows you to… docs.plane.so

  • Supports Docker, K8s / Cloud, Private On-Premise installation
  • Self-Hosted is the Community Edition (officially abbreviated as CE)
  • Self-Hosted may not include all Cloud version features
  • The default features of the Self-Hosted version are compared to the Cloud free version, if you want to use other features, you still need to upgrade to the paid version.
  • This article takes Docker + Private On-Premise installation as an example
  • Currently, the official does not provide export from Cloud to import into the Self-Hosted version, you can only achieve this through API integration
  • Official tip: Machines need to be upgraded for more than 50 users We have seen performance degradation beyond 50 users on our recommended 4 GB, 2vCPU infra. Increased infra will help with more users.
  • Uses AGPL-3.0 license open-source, the first version was launched in 2023/01, and it is still under development, no official Release version is provided yet.
  • Please note that open-source and supporting Self-Hosted does not mean free.
  • A complete configuration example Repo is attached at the end of the article.

Docker Installation

This article does not provide an introduction, please refer to the official Docker installation method to complete the local Docker environment installation and configuration. The following takes macOS Docker as an example.

Plane @ Docker Installation

Refer to the official manual.

  1. Create a directory & download the installation script
1
2
3
4
5
6
7
mkdir plane-selfhost

cd plane-selfhost

curl -fsSL -o setup.sh https://raw.githubusercontent.com/makeplane/plane/master/deploy/selfhost/install.sh

chmod +x setup.sh
  1. Ensure Docker is installed and running, then execute the script
    1
    
    ./setup.sh
    

  • Enter 1 to install (download images)

  • Wait for the images used by Plane to be pulled

  • After the images are pulled, go to the ./plane-app folder and open the .env configuration file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
APP_RELEASE=stable

WEB_REPLICAS=1
SPACE_REPLICAS=1
ADMIN_REPLICAS=1
API_REPLICAS=1

NGINX_PORT=80
WEB_URL=http://localhost
DEBUG=0
SENTRY_DSN=
SENTRY_ENVIRONMENT=production
CORS_ALLOWED_ORIGINS=http://localhost

#DB SETTINGS
PGHOST=plane-db
PGDATABASE=plane
POSTGRES_USER=plane
POSTGRES_PASSWORD=plane
POSTGRES_DB=plane
POSTGRES_PORT=5432
PGDATA=/var/lib/postgresql/data
DATABASE_URL=

# REDIS SETTINGS
REDIS_HOST=plane-redis
REDIS_PORT=6379
REDIS_URL=

# Secret Key
SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5

# DATA STORE SETTINGS
USE_MINIO=1
AWS_REGION=
AWS_ACCESS_KEY_ID=access-key
AWS_SECRET_ACCESS_KEY=secret-key
AWS_S3_ENDPOINT_URL=http://plane-minio:9000
AWS_S3_BUCKET_NAME=uploads
MINIO_ROOT_USER=access-key
MINIO_ROOT_PASSWORD=secret-key
BUCKET_NAME=uploads
FILE_SIZE_LIMIT=5242880

# Gunicorn Workers
GUNICORN_WORKERS=1

# UNCOMMENT `DOCKER_PLATFORM` IF YOU ARE ON `ARM64` AND DOCKER IMAGE IS NOT AVAILABLE FOR RESPECTIVE `APP_RELEASE`
# DOCKER_PLATFORM=linux/amd64
  • By default, Plane service starts on port :80. If there is a conflict, you can change the port.
  • Complete the setup adjustments (it is not recommended to directly change docker-compose.yml as it will be overwritten during future Plane updates)

Plane @ Docker Startup

  • Run ./setup.sh again

  • Enter 2 to start Plane:

  • After confirming successful startup, open the URL / god-mode/ for initial setup:

  • The account and password set here have the highest administrative privileges (God/Admin Mode)
  • For security reasons, the password must include special characters, be longer than 8 characters, and include numbers, uppercase and lowercase letters, otherwise it cannot be submitted
  • If this step is not completed, logging into the homepage will display Instance not configured. Please contact your administrator. ```

Plane God/Admin Mode

You can access the Plane admin interface at the URL /god-mode/. Here you can configure the entire Plane service environment.

General Settings:

General settings.

Email:

  • Email notification SMTP settings

If you don’t want to set up your own SMTP Server, you can use GMAIL SMTP directly to send emails:

  • Host: smtp.gmail.com
  • Port: 465
  • Sender email address: Display email address e.g. noreply@zhgchg.li
  • Username: Your Gmail account
  • Password: Your Gmail password, use an app password if you have two-step verification.
  • If there is no response after setting, please check the Port and Email Security settings (TLS/STARTTLS: use port 587, SSL: use port 465)

Additionally, since Plane does not currently support Slack notifications, you could set up an SMTP Server shell to convert email notifications to Slack notifications using a Python script.

Authentication

Plane service login authentication method. If you want to bind it to only allow email accounts within a Google organization, you can disable “Password based login” and enable only “Google” login. Then generate a login app that is restricted to organizational accounts from the Google login settings.

Artificial Intelligence

AI-related settings. Currently, its functionality is limited. If you have a key, you can use AI to help write Issue Descriptions on Issues.

Image in Plane

Similarly, its functionality is currently limited. If you have an Unsplash Key, you can fetch and apply images through the Unsplash API when selecting project cover images.

⚠️⚠️Disclaimer⚠️⚠️

The above is an introduction to the 2024-05-25 v0.20-Dev version. The official team is actively developing new features and optimizing user experience. Please refer to the latest version settings.

Once the God/Admin Mode settings are configured, you can use it similarly to the Cloud version.

For detailed usage operations and integration with the development process, please refer to the previous article “ Plane.so Free and Open Source Self-Hosted Asana/Jira-like Project Management Tool

Plane @ Docker Upgrade

As mentioned earlier, Plane is still in the development stage, with new versions released approximately every two to three weeks. The changes can be quite significant; it is recommended to read the Release Note carefully for changes and necessary adjustments before upgrading.

⚠️Be sure to back up before upgrading!⚠️ After upgrading, be sure to check if the scheduled backup script is still functioning properly.

⚠️Be sure to back up before upgrading!⚠️ After upgrading, be sure to check if the scheduled backup script is still functioning properly.

⚠️Be sure to back up before upgrading!⚠️ After upgrading, be sure to check if the scheduled backup script is still functioning properly.

Because Plane is in the development stage and unstable, we cannot guarantee that upgrades will not cause data loss. Therefore, it is recommended to back up before operating. The backup method will be explained below.

Upgrade Method:

  • Re-enter ./setup.sh

  • Enter 5 to upgrade Plane (this essentially just pulls new images and restarts)
  • After the images are pulled, you can restart the service
  • The .env file may change after the upgrade, please refer to the Release Note for adjustments

Plane @ Docker Backup

Starting from 0.20-dev, ./setup.sh adds a Backup Data command, but reading the official manual only mentions how to restore Backup Data to their One paid service. Therefore, I still use my own method to back up uploaded files, Redis, and backup the Postgresql Docker Container.

Backup Script

./plane-backup.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/bin/bash

# Backup Plane data
# Author: zhgchgli (https://zhgchg.li)

##### Execution Method
# ./plane-backup.sh [backup target directory path] [Plane's Docker project name] [maximum number of Plane backup files to keep, delete the oldest if exceeded]
# e.g. ./plane-backup.sh /backup/plane plane-app 14
###### Settings

# Backup target directory
backup_dir=${1:-.}

# Plane's Docker project name
docker_project_name=${2:-"plane-app"}

# Maximum number of Plane backup files to keep, delete the oldest if exceeded
keep_count=${3:-7}

######

# Check if the directory exists
if [ ! -d "$backup_dir" ]; then
  echo "Backup failed, directory does not exist: $backup_dir"
  exit;
fi

# Remove oldest
count=$(find "$backup_dir" -mindepth 1 -type d | wc -l)

while [ "$count" -ge $keep_count ]; do
    oldest_dir=$(find "$backup_dir" -mindepth 1 -maxdepth 1 -type d | while read dir; do
        # Use stat command to get modification time
        if [[ "$OSTYPE" == "darwin"* ]]; then
            # macOS system
            echo "$(stat -f %m "$dir") $dir"
        else
            # Linux system
            echo "$(stat -c %Y "$dir") $dir"
        fi
    done | sort -n | head -n 1 | cut -d ' ' -f 2-)
    
    echo "Remove oldest backup: $oldest_dir"
    rm -rf "$oldest_dir"

    count=$(find "$backup_dir" -mindepth 1 -type d | wc -l)
done
#

# Backup new
date_dir=$(date "+%Y_%m_%d_%H_%M_%S")
target_dir="$backup_dir/$date_dir"

mkdir -p "$target_dir"

echo "Backing up to: $target_dir"

# Plane's Postgresql .SQL dump
docker exec -i $docker_project_name-plane-db-1 pg_dump --dbname=postgresql://plane:plane@plane-db/plane -c > $target_dir/dump.sql

# Plane's redis
docker run --rm -v $docker_project_name-redis-1:/volume -v $target_dir:/backup ubuntu tar cvf /backup/plane-app_redis.tar /volume > /dev/null 2>&1

# Plane's uploaded files
docker run --rm -v ${docker_project_name}_uploads:/volume -v $target_dir:/backup ubuntu tar cvf /backup/plane-app_uploads.tar /volume > /dev/null 2>&1

echo "Backup Success!"

First time creating a Script file, remember to: chmod +x ./plane-backup.sh

Execution method:

1
./plane-backup.sh [Backup target folder path] [Plane Docker project name] [Maximum number of Plane backup files to retain, delete the oldest backup if exceeded]
  • Backup target folder path: e.g. /backup/plane/ or ./
  • Plane Docker project name: Plane Docker Compose Project name

  • Maximum number of Plane backup files to retain, delete the oldest backup if exceeded: Default is 7

Execution example:

1
./plane-backup.sh /backup/plane plane-app 14

  • Ensure that Plane is running when executing.

Simply add the above command to Crontab to automatically backup Plane at regular intervals.

If you encounter execution errors and cannot find the Container, please check the Plane Docker Compose Project name or verify the script and Docker container names (the official names might have changed).

Restore Script

./plane-restore.sh :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/bin/bash

# Restore Plane backup data
# Author: zhgchgli (https://zhgchg.li)

##### Execution method
# ./plane-restore.sh

# 
inputBackupDir() {
    read -p "Enter the Plane backup folder to restore (e.g. /backup/plane/2024_05_25_19_14_12): " backup_dir
}
inputBackupDir

if [[ -z $backup_dir ]]; then
    echo "Please provide the backup folder (e.g. sh /backup/docker/plane/2024_04_09_17_46_39)"
    exit;
fi

inputDockerProjectName() {
    read -p "Plane Docker project name (leave blank to use default plane-app): " input_docker_project_name
}
inputDockerProjectName
 
docker_project_name=${input_docker_project_name:-"plane-app"}

confirm() {
    read -p "Are you sure you want to restore Plane.so data? [y/N] " response
    
    # Check the response
    case "$response" in
        [yY][eE][sS]|[yY]) 
            true
            ;;
        *)
            false
            ;;
    esac
}

if ! confirm; then
    echo "Action cancelled."
    exit
fi

# Restore

echo "Restoring..."

docker cp $backup_dir/dump.sql $docker_project_name-plane-db-1:/dump.sql && docker exec -i $docker_project_name-plane-db-1 psql postgresql://plane:plane@plane-db/plane -f /dump.sql

# Restore Redis
docker run --rm -v ${docker_project_name}-redis-1:/volume -v $backup_dir:/backup alpine tar xf /backup/plane-app_redis.tar --strip-component=1 -C /volume

# Restore uploaded files
docker run --rm -v ${docker_project_name}_uploads:/volume -v $backup_dir:/backup alpine tar xf /backup/plane-app_uploads.tar --strip-component=1 -C /volume

echo "Restore Success!"

The first time you create a Script file, remember to: chmod +x ./plane-restore.sh

Execution method:

1
2
3
4
 ./plane-restore.sh
Input: The folder of the Plane backup file to be restored (e.g. /backup/plane/2024_05_25_19_14_12)
Input: The Docker project name of Plane (leave blank to use the default plane-app)
Input: Are you sure you want to execute Restore Plane.so data? [y/N] y

After seeing Restore Success!, you need to restart Plane for it to take effect.

Use Plane ./setup.sh and input 4 Restart:

Go back to the website, refresh, and log in to the Workspace to check if the restoration was successful:

Done!

⚠️ It is recommended to regularly test the backup and restore process to ensure that the backup can be used in case of an emergency.

Plane @ Docker Upgrade

As mentioned earlier, Plane is still in the development stage, and a new version is released approximately every two to three weeks, with potentially significant changes. It is recommended to read the Release Note carefully for changes and necessary adjustments before upgrading.

⚠️ Be sure to back up before upgrading! ⚠️ After upgrading, be sure to check if the scheduled backup script is still functioning properly.

⚠️ Be sure to back up before upgrading! ⚠️ After upgrading, be sure to check if the scheduled backup script is still functioning properly.

⚠️ Be sure to back up before upgrading! ⚠️ After upgrading, be sure to check if the scheduled backup script is still functioning properly.

Since Plane is in the development stage and unstable, it cannot be guaranteed that upgrading will not cause data loss. Therefore, it is recommended to back up before operating.

Upgrade method:

  • Enter ./setup.sh again

  • Input 5 to upgrade Plane (this essentially just pulls the new Images & restarts)
  • After the Images are pulled, you can restart the service
  • The .env file may change after the upgrade, please refer to the Release Note for adjustments
  • After upgrading, be sure to check if the scheduled backup script is still functioning properly
  • If the Container Name changes, you need to modify the backup, restore, and the Nginx reverse proxy script introduced below

Using Nginx + Plane for Reverse Proxy

Because we may have multiple web services to provide at the same time, such as: Self-Hosted LibreChat (ChatGPT), Self-Hosted Wiki.js, Self-Hosted Bitwarden, etc., each service requires port 80 by default. If we do not want to specify the port in the URL when using it, we need to start a Docker Nginx as a reverse proxy for web services.

The effect is as follows:

1
2
3
4
5
chat.zhgchg.li -> LibreChat :8082
wiki.zhgchg.li -> Wiki.js :8083
pwd.zhgchg.li -> Bitwarden :8084

plane.zhgchg.li -> Plane.so :8081

To achieve the above effect, you need to move the ./plane-selfhost directory to a unified directory, named webServices here.

Final directory structure preview:

Adjust the webServices/plane-selfhost/plane-app/.env environment configuration file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
APP_RELEASE=stable

WEB_REPLICAS=1
SPACE_REPLICAS=1
ADMIN_REPLICAS=1
API_REPLICAS=1

NGINX_PORT=8081
WEB_URL=http://plane.zhgchg.li
DEBUG=0
SENTRY_DSN=
SENTRY_ENVIRONMENT=production
CORS_ALLOWED_ORIGINS=http://plane.zhgchg.li

#DB SETTINGS
PGHOST=plane-db
PGDATABASE=plane
POSTGRES_USER=plane
POSTGRES_PASSWORD=plane
POSTGRES_DB=plane
POSTGRES_PORT=5432
PGDATA=/var/lib/postgresql/data
DATABASE_URL=

# REDIS SETTINGS
REDIS_HOST=plane-redis
REDIS_PORT=6379
REDIS_URL=

# Secret Key
SECRET_KEY=60gp0byfz2dvffa45cxl20p1scy9xbpf6d8c5y0geejgkyp1b5

# DATA STORE SETTINGS
USE_MINIO=1
AWS_REGION=
AWS_ACCESS_KEY_ID=access-key
AWS_SECRET_ACCESS_KEY=secret-key
AWS_S3_ENDPOINT_URL=http://plane-minio:9000
AWS_S3_BUCKET_NAME=uploads
MINIO_ROOT_USER=access-key
MINIO_ROOT_PASSWORD=secret-key
BUCKET_NAME=uploads
FILE_SIZE_LIMIT=5242880

# Gunicorn Workers
GUNICORN_WORKERS=1

# UNCOMMENT `DOCKER_PLATFORM` IF YOU ARE ON `ARM64` AND DOCKER IMAGE IS NOT AVAILABLE FOR RESPECTIVE `APP_RELEASE`
# DOCKER_PLATFORM=linux/amd64
  • Replace the URL with the one we want, using plane.zhgchg.li as an example
  • Change NGINX_PORT to 8081 to free up the original 80 for the reverse proxy Nginx

webServices/ Create a docker-compose.yml file to place Nginx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: '3.8'

services:
  webServices-nginx:
    image: nginx
    restart: unless-stopped
    volumes:
      - ./nginx/conf.d/plane.zhgchg.li.conf:/etc/nginx/conf.d/plane.zhgchg.li.conf

    ports:
      - 80:80
      - 443:443

    networks:
      - plane-app_default # Network used by plane
networks:
  plane-app_default:
    external: true
  • We need to add the Plane app network to Nginx

webServices/ Create a /conf.d directory & plane.zhgchg.li.conf file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# For plane.zhgchg.li

# http example:
server {
    listen 80;
    server_name plane.zhgchg.li;

    client_max_body_size 0;

    location / {
 proxy_pass http://plane-app-proxy-1; # plane proxy-1 service name
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}


# https & http example:
# server {
#     listen 443 ssl;
#     server_name plane.zhgchg.li;

#     #ssl
#     ssl_certificate             /etc/nginx/conf/ssl/zhgchgli.crt; # Replace with your domain's crt & remember to add the key to docker-compose.yml volumes and mount into Docker
#     ssl_certificate_key         /etc/nginx/conf/ssl/zhgchgli.key; # Replace with your domain's key & remember to add the key to docker-compose.yml volumes and mount into Docker
#     ssl_prefer_server_ciphers   on;
#     ssl_protocols               TLSv1.1 TLSv1.2;
#     ssl_ciphers                 "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
#     ssl_ecdh_curve              secp384r1; # Requires nginx >= 1.1.0
#     ssl_session_timeout         10m;
#     ssl_session_cache           shared:SSL:10m;
#     add_header                  Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";

#     client_max_body_size 0;

#     location / {
#  proxy_pass http://plane-app-proxy-1; # plane proxy-1 service name
#         proxy_set_header Host $host;
#         proxy_set_header X-Real-IP $remote_addr;
#         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#         proxy_set_header X-Forwarded-Proto $scheme;
#     }
# }

# server {
#     listen 80;
#     server_name plane.zhgchg.li;
#     return 301 https://plane.zhgchg.li$request_uri;
# }

Because there are multiple docker-compose.yml files that need to be started individually, followed by starting the Nginx reverse proxy, we can put all the startup scripts into a single Shell Script.

Create the /start.sh file under webServices/:

1
2
3
4
5
6
7
8
9
#!/bin/sh

# Encapsulate the startup Script

# Start Plane and other services first
docker compose -f ./plane-selfhost/plane-app/docker-compose.yaml --env-file ./plane-selfhost/plane-app/.env up -d

# Start Nginx last
docker compose -f ./docker-compose.yml --env-file ./.env up -d

When creating the Script file for the first time, remember to: chmod +x ./start.sh

You can also create one to stop the services, create the /stop.sh file under webServices/:

1
2
3
4
5
6
7
#!/bin/sh

# Encapsulate the stop Script

docker compose -f ./plane-selfhost/plane-app/docker-compose.yaml --env-file ./plane-selfhost/plane-app/.env down

docker compose -f ./docker-compose.yml --env-file ./.env down

When creating the Script file for the first time, remember to: chmod +x ./stop.sh

Start

  • After encapsulating the Nginx reverse proxy, Plane service, and others, you can directly run ./start.sh to start all services
1
./start.sh

DNS Settings

If hosted on an internal network, you need to ask the IT department to add a DNS record for plane.zhgchg.li -> server IP address in the internal DNS.

1
plane.zhgchg.li server IP address

If you are testing on your local computer, you can add the following to the /private/etc/hosts file:

1
127.0.0.1 plane.zhgchg.li

After completing the DNS settings, you can open Plane by visiting plane.zhgchg.li!

Common Issues

  1. Nginx fails to start and keeps Restarting, check the Log showing nginx: [emerg] host not found in upstream This means the Nginx reverse proxy service cannot find the Plane service. Check if the name http://plane-app-proxy-1 is correct and if the Nginx docker-compose.yml network settings are correct.
  2. 502 Bad Gateway appears The startup order is incorrect (ensure the Nginx reverse proxy is started last) or the Plane process has restarted. Try restarting it again.
  3. Nginx default homepage welcome to nginx! appears, using the reverse proxy you will no longer be able to access Plane using the original IP:80 method, you need to use the URL.
  4. The URL cannot be resolved or the host cannot be found, please check if the DNS network settings are normal.

⚠️⚠️Security Issues⚠️⚠️

Since the Plane project is under development and is an open-source project, it is uncertain whether there are any serious system vulnerabilities, which could potentially become an entry point for intrusion. Therefore, it is not recommended to set up Plane.so Self-Hosted on a public network. It is better to add an extra layer of security verification (Tunnel or certificate or VPN) to access it; even if it is set up on an internal network, it is best to isolate it.

As a project under development, there are inevitably bugs, experience, and security issues. Please be patient with the Plane.so team. If you have any issues, feel free to report them below:

Complete Self-Hosted Repo Example Download

Plane.so Usage and Integration with Scrum Process

If you have any questions or feedback, feel free to contact me.

===

本文中文版本

===

This article was first published in Traditional Chinese on Medium ➡️ View Here


This post is licensed under CC BY 4.0 by the author.

Plane.so Free Open Source and Self-Hosted Project Management Tool Similar to Asana/Jira

Exploring the Use of NSTextList or NSTextTab for List Indentation with NSAttributedString in iOS