Plane.so Docker Self-Hosted|Complete Guide to Setup, Backup & Nginx Proxy
Learn how to self-host Plane.so with Docker, including step-by-step backup, restoration, and Nginx domain reverse proxy configuration to ensure smooth and secure deployment.
点击这里查看本文章简体中文版本。
點擊這裡查看本文章正體中文版本。
This post was translated with AI assistance — let me know if anything sounds off!
Plane.so Docker Self-Hosted Setup Record
Plane Self-Hosted Docker Setup, Backup, Restore, and Nginx Domain Reverse Proxy Configuration Tutorial
️⚠️️️️⚠️️️️⚠️️️️2025 Update️ ⚠️️️️⚠️️️️⚠️️️️
Plane.so is no longer recommended because it initially relied on open-source projects and self-hosting support to grow. However, later versions placed many features behind paid plans. Even self-hosted setups require purchasing licenses to unlock functions, and there are user limits for self-hosting, which goes against the original vision. Additionally, the pricing plans are very confusing, with lifetime, Pro, Business options… making it uncertain whether new plans will be introduced and new features will only be available in those plans.
Preface
Plane.so is a free, open-source, self-hosted project management tool similar to Asana, Jira, and ClickUp. It was founded in 2022, with the first version released in 2023, and is currently still in development.
For detailed usage and development process, please refer to the previous article “Plane.so Free Open Source and Self-Hosted Asana/Jira-like Project Management Tool”. This article only records the process of self-hosting Plane.so using Docker.
Self-Hosted Plane
Supports Docker, K8s / Cloud, and private on-premise installation
Self-Hosted is the Community Edition (officially abbreviated as CE) version
Self-Hosted may not include all features of the Cloud version
The Self-Hosted version’s features are set by default to match the Cloud free version. To use other features, you still need to upgrade to a paid version.
This article uses Docker + private local installation as an example
Currently, the official version does not support exporting from Cloud and importing to the Self-Hosted version; it can only be achieved through API integration.
Official Notice: More than 50 users require upgraded machine performance
We have seen performance degradation beyond 50 users on our recommended 4 GB, 2vCPU infra. Increased infra will help with more users.
Released under the AGPL-3.0 license, the first version was launched in January 2023. It is still under development and no official release version is available yet.
Please note that open source and support for self-hosted do not mean free of charge.
The complete setup example repo is attached at the end.
Docker Installation
This article will not provide a detailed introduction. Please refer to the official Docker installation guide to complete the local Docker environment setup and configuration. The following example uses Docker on macOS.
Plane @ Docker Installation
Refer to the official manual.
- Create Table of Contents & Download 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
- Ensure Docker is installed and running, then execute the script
1
./setup.sh
- Enter
1
to install (download Images)
- Waiting for the images used by Plane to be fully loaded
- After Images 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, the Plane service starts using port
:80
. If there is a conflict, you can change the port.Complete the setup adjustments (directly modifying
docker-compose.yml
is not recommended because future Plane updates will overwrite this file)
Plane @ Docker Startup
- Run ./setup.sh again
- Enter
2
to start Plane:
- After completing the startup and confirming all services are running successfully, open the URL
/
god-mode/
for the 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, contain numbers, and have both uppercase and lowercase letters; otherwise, it cannot be submitted.
This step is not set to enter the homepage login, showing
Instance not configured. Please contact your administrator.
Plane God/Admin Mode Management Backend
You can access it from the Plane URL /god-mode/
, where 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: The displayed email, e.g.
noreply@zhgchg.li
Username: Your Gmail account
Password: Your Gmail password, if you have two-factor authentication, use an app password.
If there is no response after setup, please check the Port and Email Security settings (TLS/STARTTLS: use port 587, SSL: use port 465).
Another idea is that since Plane does not currently support Slack notifications, you can set up an SMTP server to receive emails and use a Python script to convert the emails into Slack notifications.
Authentication
Plane service login authentication can be restricted to only Google organization email accounts by disabling “Password based login” and enabling only the “Google” login option. Then, generate a login app from the Google login side that limits access to organization accounts only.
Artificial Intelligence
AI-related settings currently have limited functionality. Once the key is set, AI can assist in writing Issue Descriptions directly on the Issue page.
Image in Plane
Currently, this feature is not very useful. By adding an Unsplash Key, you can fetch images through the Unsplash API when selecting a project cover image and apply them.
⚠️⚠️Disclaimer⚠️⚠️
The above is the usage introduction for version 0.20-Dev dated 2024-05-25. The official team is actively developing new features and optimizing user experience. Please refer to the latest version settings.
After setting up God/Admin Mode, you can use it almost like the Cloud version.
For detailed usage and development process introduction, please refer to the previous article “Plane.so Free Open-Source and Self-Hosted Asana/Jira-like Project Management Tool”.
Plane @ Docker Upgrade
As mentioned earlier, Plane is still in development and releases new versions roughly every two to three weeks, with potentially significant changes. It is recommended to carefully read the Release Note for changes and required configuration adjustments before upgrading.
⚠️ Be sure to back up before upgrading! ⚠️ After upgrading, make sure to check if the scheduled backup script still works properly.
⚠️ Be sure to back up before upgrading! ⚠️ After upgrading, make sure to check if the scheduled backup scripts still work properly.
⚠️ Be sure to back up before upgrading! ⚠️ After upgrading, make sure to check if the scheduled backup scripts still work properly.
Since Plane is in the development stage and unstable, data loss cannot be ruled out during upgrades. Therefore, it is strongly recommended to back up your data before proceeding. Backup methods will be explained below.
Upgrade Method:
- Run ./setup.sh again
Enter
5
to upgrade the Plane (which basically just pulls new Images and restarts)After the Images are pulled, you can restart the service.
The upgraded .env file may have changes; please refer to the Release Note for adjustments.
Plane @ Docker Backup
Starting from 0.20-dev
, ./setup.sh
added a Backup Data command. However, the official manual only explains how to restore Backup Data to their One paid service. Therefore, I still use my own manual method to back up and upload files, Redis, and 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)
##### Usage
# ./plane-backup.sh [target backup folder path] [Plane Docker project name] [max number of Plane backup files to keep, delete oldest if exceeded]
# e.g. ./plane-backup.sh /backup/plane plane-app 14
###### Configuration
# Target backup folder
backup_dir=${1:-.}
# Plane Docker project name
docker_project_name=${2:-"plane-app"}
# Max number of Plane backup files to keep, delete oldest if exceeded
keep_count=${3:-7}
######
# Check if 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!"
The first time you create the script file, remember to: chmod +x ./plane-backup.sh
Execution Method:
1
./plane-backup.sh [target backup folder path] [Plane's Docker project name] [maximum number of Plane backup files to keep, delete oldest backups if exceeded]
Backup to target folder path: e.g
/backup/plane/
or./
Plane Docker Project Name: Plane Docker Compose Project Name
- Plane backup files maximum retention count, delete the oldest backup when exceeded: default 7 copies
Example Execution:
1
./plane-backup.sh /backup/plane plane-app 14
- Make sure the Plane is in an active state during execution.
Just add the above command to Crontab to schedule automatic backups of Plane.
If you encounter an execution error stating that the container cannot be found, please verify the Plane Docker Compose project name or check the script and Docker container names (the official names may 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)
##### How to run
# ./plane-restore.sh
#
inputBackupDir() {
read -p "Folder of the Plane backup 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 "Docker project name for Plane (leave empty 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!"
Remember to run chmod +x ./plane-restore.sh
the first time you create the script file.
Execution Method:
1
2
3
4
./plane-restore.sh
Input: Folder of the Plane backup file to restore (e.g. /backup/plane/2024_05_25_19_14_12)
Input: Docker project name for Plane (leave blank to use default plane-app)
Input: Are you sure you want to restore Plane.so data? [y/N] y
After seeing Restore Success!
, you need to restart Plane for the changes to take effect.
Use Plane ./setup.sh
and enter 4
Restart:
Go back to the website, refresh, and log in to Workspace to check if the recovery was successful:
Done!
⚠️ It is recommended to regularly test the backup and restore process to ensure backups are useful in case of emergencies.
Plane @ Docker Upgrade
As mentioned earlier, Plane is still in development and releases a new version every two to three weeks. Changes can be significant; it is recommended to carefully read the Release Note for updates and required configuration adjustments before upgrading.
⚠️ Be sure to back up before upgrading! ⚠️ After upgrading, make sure to check if the scheduled backup scripts still work properly.
⚠️ Be sure to back up before upgrading! ⚠️ After upgrading, make sure to check if the scheduled backup script still works properly.
⚠️ Backup before upgrading! ⚠️ After upgrading, make sure to verify that the scheduled backup script still functions correctly.
Since Plane is in the development stage and unstable, it cannot guarantee that upgrades will not cause data loss. Therefore, it is recommended to back up your data before operating.
Upgrade Method:
- Run ./setup.sh again
Enter
5
to upgrade the Plane (which basically just downloads new images & restarts).After “Images Pulled” is complete, you can restart the service.
The upgraded .env file may have changes. Please refer to the Release Note for adjustments.
After the upgrade, be sure to check if the scheduled backup script still works properly.
If the container name changes, you need to update the backup, restore, and Nginx reverse proxy scripts mentioned below.
Using Nginx + Plane for Reverse Proxy
Because we may need to provide multiple web services simultaneously, such as Self-Hosted LibreChat (ChatGPT), Self-Hosted Wiki.js, Self-Hosted Bitwarden, etc., each service defaults to port 80. If we don’t want to specify the port in the URL when using them, we need to run a Docker Nginx as a reverse proxy for the web services.
Effect 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, first move the ./plane-selfhost
directory into a unified directory, here named webServices
.
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 desired one, using
plane.zhgchg.li
as an example.Change NGINX_PORT to
8081
and use the original80
for the reverse proxy Nginx.
webServices/
Create a docker-compose.yml
file here to host 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’s network to Nginx
webServices/
Create the /conf.d
directory and the 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 mount the key into Docker via docker-compose.yml volumes
# ssl_certificate_key /etc/nginx/conf/ssl/zhgchgli.key; # Replace with your domain's key & remember to mount the key into Docker via docker-compose.yml volumes
# 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;
# }
proxy_pass Enter the service entry in the Plane network
Here, only HTTP is used as an example. To support HTTPS, you can refer to the method of self-signed certificates.
Since multiple docker-compose.yml files need to be started individually before launching the Nginx reverse proxy, we can put all the startup scripts into a single Shell Script.
webServices/
create a /start.sh
file:
1
2
3
4
5
6
7
8
9
10
#!/bin/sh
# Startup script wrapper
# 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
Remember to run chmod +x ./start.sh
the first time you create the script file.
You can also create one to stop the service. Create the /stop.sh
file under webServices/
:
1
2
3
4
5
6
7
#!/bin/sh
# Stop script wrapper
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
Remember to run chmod +x ./stop.sh
the first time you create the script file.
Activation
- After packaging Nginx reverse proxy + Plane service + others, you can start all services directly by running
./start.sh
.
1
./start.sh
DNS Settings
Hosted on the internal network, the IT department needs to add a DNS record in the internal DNS for plane.zhgchg.li -> server IP address.
1
plane.zhgchg.li server IP address
If you are testing locally on your own computer, you can add the following to the /private/etc/hosts file:
1
127.0.0.1 plane.zhgchg.li
After completing the DNS setup, you can access Plane by visiting plane.zhgchg.li!
Frequently Asked Questions
Nginx failed to start and keeps restarting. Checking the log shows nginx: [emerg] host not found in upstream.
This means the Nginx reverse proxy service cannot find the Plane service. You need to verify if the namehttp://plane-app-proxy-1
is correct and if the network settings in the Nginx docker-compose.yml are properly configured.502 Bad Gateway Appears
The startup order is incorrect (make sure Nginx reverse proxy starts last) or the Plane process has restarted. Try restarting, and it should work.When the Nginx default homepage
welcome to nginx!
appears, you can no longer access Plane via the original IP:80 address when using reverse proxy. You must use the URL instead.The URL cannot be resolved, and the host cannot be found. Please check if the DNS network settings are correct.
⚠️⚠️Security Issues⚠️⚠️
Since the Plane project is still under development and open source, it is uncertain whether there are serious system vulnerabilities. The worst case could be an entry point for intrusion. Therefore, it is not recommended to host Plane.so Self-Hosted on a public network. It is better to add an extra layer of security verification (Tunnel, certificate, or VPN) to allow access. If hosted on an internal network, it should also be properly isolated.
During development, projects inevitably have bugs, experience, and security issues. Please be patient with the Plane.so team; if you encounter any problems, feel free to report them below:
Issue Reporting: https://github.com/makeplane/plane/issues
Official Discord: https://discord.com/invite/A92xrEGCge
Complete Self-Hosted Repo Example Download
Plane.so Usage and Scrum Process Integration Tutorial
If you have any questions or feedback, feel free to contact me.
This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.