plane
plane copied to clipboard
[feature]: simplify the docker compose setup
Is there an existing issue for this?
- [X] I have searched the existing issues
Summary
I think the docker compose file could be improved/simplified.
Why should this be worked on?
Right now the compose file is a mess, and to selfhost plane you need to clone the whole repo instead of just using one compose file.
What could be changed/improved?
~~What bothers me the most is that I need to pull the whole repo instead of that everything already was included in the docker image.~~ I've taken a quick look and it seems like this can be easily fixed by removing the build from the api container (someone probably forgot to remove that). After removing that I only needed the docker compose file (so maybe change the readme to only download the compose file, instead of the whole project?)
Besides that, is it possible to use my already existing proxy, which is hosting other services? Instead of just running a nginx container and expecting nothing else to be hosted on the server (and using port 80/443).
Also, why not store all the environment variables in the .env file and use the env file attribute instead of the source .env
.
The ports for redis and postgres don't have to be exposed (maybe you want this for the development environment, but for selfhosting it's better to remove it/disable it), as the containers can communicate freely in the docker network with each other. This way you can't connect to redis/postgres from the outside.
Besides that, is it possible to use my already existing proxy, which is hosting other services? Instead of just running a nginx container and expecting nothing else to be hosted on the server (and using port 80/443).
As part of self-hosting setup for v0.5, I've removed all external port bindings from the docker-compose file (as all services talk to each other via the docker network created for Plane), and exposed the nginx container on an "internal" port (e.g. 8000 with iptables rules to block all external traffic) for which all traffic is proxied to by the top-level nginx instance which handles the domain name in question. The only thing you have to do is change all public URLs to point to the specific domain or subdomain hosting the Plane instance so that all traffic between the frontend and backend is correctly routed according to your top-level nginx configuration.
This has changed slightly with v0.6-develop but shouldn't be significantly more difficult to implement.
The main advantage of this is that you don't have to "import" the apiservice proxy setup into your top-level nginx config and keep it up to date when Plane changes
The main advantage of this is that you don't have to "import" the apiservice proxy setup into your top-level nginx config and keep it up to date when Plane changes
But that requires you to run an extra nginx container tho, doesn't it? Since you're already running nginx (or another reverse proxy), Then you're proxying to the nginx container, which then proxies to the frontend or backend.
+1 to this.
I'm using traefik as the proxy container, and am still struggling to get this working.
If you use this compose file: https://github.com/Robin-Sch/plane/blob/develop/docker-compose-hub.yml and proxy plane.example.com
to the frontend (port 3000) and plane-api.example.com
to the backend (port 8000), and run ./setup.sh plane-api.example.com
it should work
I could not get it to work with separate subdomains, because of CORS issues. So what I did was this:
version: '3.8'
services:
plane-frontend:
container_name: plane-frontend
image: makeplane/plane-frontend:0.6
restart: unless-stopped
command: "/usr/local/bin/start.sh"
environment:
NEXT_PUBLIC_API_BASE_URL: "${WEB_ENTRYPOINT}://plane.${DOMAIN}"
NEXT_PUBLIC_GOOGLE_CLIENTID: 0
NEXT_PUBLIC_GITHUB_APP_NAME: 0
NEXT_PUBLIC_GITHUB_ID: 0
NEXT_PUBLIC_SENTRY_DSN: 0
NEXT_PUBLIC_ENABLE_OAUTH: 0
NEXT_PUBLIC_ENABLE_SENTRY: 0
networks:
- home-server
depends_on:
plane-backend:
condition: service_started
labels:
traefik.enable: "true"
traefik.docker.network: "home-server"
traefik.http.routers.plane-frontend.rule: "Host(`plane.${DOMAIN}`) && !PathPrefix(`/api`)"
traefik.http.routers.plane-frontend.priority: "1"
traefik.http.routers.plane-frontend.entrypoints: "${WEB_ENTRYPOINT}"
traefik.http.routers.plane-frontend.tls: "${TLS:-false}"
traefik.http.routers.plane-frontend.tls.certResolver: "letsencrypt"
traefik.http.routers.plane-frontend.middlewares: "retry@file, cors@file ${MIDDLEWARE_SECURED}"
traefik.http.services.plane-frontend.loadbalancer.server.port: "3000"
plane-backend:
container_name: plane-backend
image: makeplane/plane-backend:0.6
restart: unless-stopped
environment:
DJANGO_SETTINGS_MODULE: plane.settings.production
DATABASE_URL: postgres://${HOME_SERVER_PLANE_DATABASE_USERNAME}:${HOME_SERVER_PLANE_DATABASE_PASSWORD}@postgres:5432/plane
REDIS_URL: redis://redis:6379/
EMAIL_HOST: "${EMAIL_SERVER_HOST}"
EMAIL_HOST_USER: "${EMAIL_SERVER_USERNAME}"
EMAIL_HOST_PASSWORD: "${EMAIL_SERVER_PASSWORD}"
EMAIL_FROM: "${EMAIL_FROM}"
EMAIL_HOST_PORT: "${EMAIL_SERVER_PORT}"
EMAIL_USE_TLS: "${EMAIL_SERVER_USE_TLS}"
AWS_REGION: "${AWS_REGION}"
AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}"
AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}"
AWS_S3_BUCKET_NAME: "${AWS_S3_BUCKET_NAME}"
WEB_URL: "${WEB_ENTRYPOINT}://plane.${DOMAIN}"
GITHUB_CLIENT_SECRET: "${GITHUB_CLIENT_SECRET}"
DISABLE_COLLECTSTATIC: 1
DOCKERIZED: 1
OPENAI_API_KEY: "${OPENAI_API_KEY}"
GPT_ENGINE: "${GPT_ENGINE}"
SECRET_KEY: "${HOME_SERVER_PLANE_SECRET_KEY}"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
networks:
- database
- home-server
profiles:
- default
command: ./bin/takeoff
labels:
traefik.enable: "true"
traefik.docker.network: "home-server"
traefik.http.routers.plane-backend.rule: "Host(`plane.${DOMAIN}`) && PathPrefix(`/api`)"
traefik.http.routers.plane-backend.priority: "2"
traefik.http.routers.plane-backend.entrypoints: "${WEB_ENTRYPOINT}"
traefik.http.routers.plane-backend.tls: "${TLS:-false}"
traefik.http.routers.plane-backend.tls.certResolver: "letsencrypt"
traefik.http.routers.plane-backend.middlewares: "retry@file, cors@file ${MIDDLEWARE_SECURED}"
traefik.http.services.plane-backend.loadbalancer.server.port: "8000"
plane-worker:
container_name: plane-worker
image: makeplane/plane-worker:0.6
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
plane-backend:
condition: service_started
command: ./bin/worker
networks:
- database
- home-server
environment:
DJANGO_SETTINGS_MODULE: plane.settings.production
DATABASE_URL: postgres://${HOME_SERVER_PLANE_DATABASE_USERNAME}:${HOME_SERVER_PLANE_DATABASE_PASSWORD}@postgres:5432/plane
REDIS_URL: redis://redis:6379/
EMAIL_HOST: "${EMAIL_SERVER_HOST}"
EMAIL_HOST_USER: "${EMAIL_SERVER_USERNAME}"
EMAIL_HOST_PASSWORD: "${EMAIL_SERVER_PASSWORD}"
EMAIL_FROM: "${EMAIL_FROM}"
EMAIL_HOST_PORT: "${EMAIL_SERVER_PORT}"
EMAIL_USE_TLS: "${EMAIL_SERVER_USE_TLS}"
AWS_REGION: "${AWS_REGION}"
AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}"
AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}"
AWS_S3_BUCKET_NAME: "${AWS_S3_BUCKET_NAME}"
WEB_URL: "${WEB_ENTRYPOINT}://plane.${DOMAIN}"
GITHUB_CLIENT_SECRET: "${GITHUB_CLIENT_SECRET}"
DISABLE_COLLECTSTATIC: 1
DOCKERIZED: 1
OPENAI_API_KEY: "${OPENAI_API_KEY}"
GPT_ENGINE: "${GPT_ENGINE}"
SECRET_KEY: "${HOME_SERVER_PLANE_SECRET_KEY}"
Essentially, both the frontend and backend are on plane.example.com
. Since the frontend always appends /api
to any backend calls, we can make traefik forward requests to plane.example.com/api
to the backend and everything that's on plane.example.com
that is not under /api
to the frontend.
I'm now struggling with getting plane to work with gmail 😄
Figured out the issue with the email (and fixed my previous comment). Turns out that, when your environment variable definition contains only a single variable that needs expansion, you need to wrap it in double quotes. ie, EMAIL_HOST: ${EMAIL_SERVER_HOST}
needs to be EMAIL_HOST: "${EMAIL_SERVER_HOST}"
, otherwise the expansion doesn't seem to be done. 🤷
Wouldn't building a plane-nginx
as a part of the github workflow highly simplify the setup process?
If for example you look at the way immich
is being deployed (another self-hosted solution that requires proxy), their setup is quite the same.
They have various backend services, that are routed by one single NGINX proxy and are tagged as immich-proxy
.
By building all the services and pushing them to the image registry, a single docker-compose.yml is available which can be used on various platforms like portainer and any system that allows for docker-compose entries.
If the docs then reflect all available env variables and their settings (and an explanation of what they do) a user could quite easily set the environment variables that are required.
For users that do not need a reverse proxy, they simple comment out the nginx service, and follow the documentation steps (if available) to link their own existing reverse proxy to the respective services that need to be served for plane to work as intended.
Wouldn't building a
plane-nginx
as a part of the github workflow highly simplify the setup process?
yes I totally agree on this, because now, people still have to download the WHOLE repo just for the nginx config file
I wholeheartedly agree with this. I have submitted a PR #1181 to develop the proxy as a service, which will be incorporated into our upcoming release. Thank you for your valuable inputs.
The readme has the following download/setup steps:
git clone https://github.com/makeplane/plane
cd plane
chmod +x setup.sh
./setup.sh http://localhost
docker compose up -d
This requires to download the whole repo, which is kinda weird in my opinion but okay. This could easily be fixed by just having to download a docker-compose file, and a .env.example
/ .env
file, and be a lot quicker to download and require less disk space.
The documentation site does already have an improvement (downloading with the --depth 1 flag), but this will still download the WHOLE repo (except for commit history, etc., afaik).
Besides that, the docker compose up -d
command will use the docker-compose.yml
file, and NOT the docker-compose-hub.yml
file, which means docker will build the images instead of downloading them (I think it's better to just download the pre build images instead of building them manually).
See the documentation: https://docs.plane.so/self-hosting
Personally, I think it's the best to have the plane-proxy container at the top, so it's easier to find and comment everything out when you're already using a reverse proxy. And a mention of using your own reverse proxy on the documentation site would be nice.
After finally getting Plane to run locally, here's my $0.02...
No scripts, please
When I'm self-hosting something, I don't want to rely on a separate script that "makes things easier" for me. Most people who are self-hosting are quite comfortable (and prefer) being in control of the details. The compose file should simply reference existing container images. Why do I need to clone a repo and run a script? 🤮
My own reverse proxy should (almost) just work
This goes along with controlling the details. My personal preference (as others have stated) would be to have the proxy to be "off" by default. But even so, the instructions on how to get this deployed behind an existing reverse proxy are incomplete. Simply saying "comment this nginx container out if you have your own reverse proxy" doesn't quite get you there. See below to see "Things I had to do..."
Too many variables
The compose file is a bit of an eyesore. Since I don't know how things work internally, I don't dare drop any of the ENV variables that are being defaulted in the compose file. Let me override variables here if I want, but please default them to your sane values inside your container/script. The way things look now, it is a bit much and will likely scare some folks away from the project entirely.
Things I had to do...
I just dropped the compose YML into a Portainer stack and worked through things from there.
-
Had to remove the
platform
property fromservices
items where it existed. -
Added to
web
service to allow me to use my existing reverse proxy.
environment:
- HOSTNAME=0.0.0.0
ports:
- 5588:3000
Seems like the application in this container only binds to the stack's network IP by default. So I had to pass in HOSTNAME
so it would bind to all of the container's IP addresses and then let my reverse proxy consume (in my case) port 5588 for Plane.
- Added to
api
service for my reverse proxy.
ports:
- 5589:8000
- Adjusted default ENV vars since the current way they are defined is just flat breaking things.
- DATABASE_URL=postgresql://plane:plane@plane-db/plane
- REDIS_URL=redis://plane-redis:6379/
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-https://my.domain.com}
See: #2809 #3125
To add to this (and my comment above yours), I just took a quick look at the docker compose file, and it's building all the images again locally instead of downloading them
@Robin-Sch you can use the docker-compose.yml which uses the images from docker hub. Closing this issue as it is resolved
I could not get it to work with separate subdomains, because of CORS issues. So what I did was this:
version: '3.8' services: plane-frontend: container_name: plane-frontend image: makeplane/plane-frontend:0.6 restart: unless-stopped command: "/usr/local/bin/start.sh" environment: NEXT_PUBLIC_API_BASE_URL: "${WEB_ENTRYPOINT}://plane.${DOMAIN}" NEXT_PUBLIC_GOOGLE_CLIENTID: 0 NEXT_PUBLIC_GITHUB_APP_NAME: 0 NEXT_PUBLIC_GITHUB_ID: 0 NEXT_PUBLIC_SENTRY_DSN: 0 NEXT_PUBLIC_ENABLE_OAUTH: 0 NEXT_PUBLIC_ENABLE_SENTRY: 0 networks: - home-server depends_on: plane-backend: condition: service_started labels: traefik.enable: "true" traefik.docker.network: "home-server" traefik.http.routers.plane-frontend.rule: "Host(`plane.${DOMAIN}`) && !PathPrefix(`/api`)" traefik.http.routers.plane-frontend.priority: "1" traefik.http.routers.plane-frontend.entrypoints: "${WEB_ENTRYPOINT}" traefik.http.routers.plane-frontend.tls: "${TLS:-false}" traefik.http.routers.plane-frontend.tls.certResolver: "letsencrypt" traefik.http.routers.plane-frontend.middlewares: "retry@file, cors@file ${MIDDLEWARE_SECURED}" traefik.http.services.plane-frontend.loadbalancer.server.port: "3000" plane-backend: container_name: plane-backend image: makeplane/plane-backend:0.6 restart: unless-stopped environment: DJANGO_SETTINGS_MODULE: plane.settings.production DATABASE_URL: postgres://${HOME_SERVER_PLANE_DATABASE_USERNAME}:${HOME_SERVER_PLANE_DATABASE_PASSWORD}@postgres:5432/plane REDIS_URL: redis://redis:6379/ EMAIL_HOST: "${EMAIL_SERVER_HOST}" EMAIL_HOST_USER: "${EMAIL_SERVER_USERNAME}" EMAIL_HOST_PASSWORD: "${EMAIL_SERVER_PASSWORD}" EMAIL_FROM: "${EMAIL_FROM}" EMAIL_HOST_PORT: "${EMAIL_SERVER_PORT}" EMAIL_USE_TLS: "${EMAIL_SERVER_USE_TLS}" AWS_REGION: "${AWS_REGION}" AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" AWS_S3_BUCKET_NAME: "${AWS_S3_BUCKET_NAME}" WEB_URL: "${WEB_ENTRYPOINT}://plane.${DOMAIN}" GITHUB_CLIENT_SECRET: "${GITHUB_CLIENT_SECRET}" DISABLE_COLLECTSTATIC: 1 DOCKERIZED: 1 OPENAI_API_KEY: "${OPENAI_API_KEY}" GPT_ENGINE: "${GPT_ENGINE}" SECRET_KEY: "${HOME_SERVER_PLANE_SECRET_KEY}" depends_on: postgres: condition: service_healthy redis: condition: service_started networks: - database - home-server profiles: - default command: ./bin/takeoff labels: traefik.enable: "true" traefik.docker.network: "home-server" traefik.http.routers.plane-backend.rule: "Host(`plane.${DOMAIN}`) && PathPrefix(`/api`)" traefik.http.routers.plane-backend.priority: "2" traefik.http.routers.plane-backend.entrypoints: "${WEB_ENTRYPOINT}" traefik.http.routers.plane-backend.tls: "${TLS:-false}" traefik.http.routers.plane-backend.tls.certResolver: "letsencrypt" traefik.http.routers.plane-backend.middlewares: "retry@file, cors@file ${MIDDLEWARE_SECURED}" traefik.http.services.plane-backend.loadbalancer.server.port: "8000" plane-worker: container_name: plane-worker image: makeplane/plane-worker:0.6 depends_on: postgres: condition: service_healthy redis: condition: service_started plane-backend: condition: service_started command: ./bin/worker networks: - database - home-server environment: DJANGO_SETTINGS_MODULE: plane.settings.production DATABASE_URL: postgres://${HOME_SERVER_PLANE_DATABASE_USERNAME}:${HOME_SERVER_PLANE_DATABASE_PASSWORD}@postgres:5432/plane REDIS_URL: redis://redis:6379/ EMAIL_HOST: "${EMAIL_SERVER_HOST}" EMAIL_HOST_USER: "${EMAIL_SERVER_USERNAME}" EMAIL_HOST_PASSWORD: "${EMAIL_SERVER_PASSWORD}" EMAIL_FROM: "${EMAIL_FROM}" EMAIL_HOST_PORT: "${EMAIL_SERVER_PORT}" EMAIL_USE_TLS: "${EMAIL_SERVER_USE_TLS}" AWS_REGION: "${AWS_REGION}" AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" AWS_S3_BUCKET_NAME: "${AWS_S3_BUCKET_NAME}" WEB_URL: "${WEB_ENTRYPOINT}://plane.${DOMAIN}" GITHUB_CLIENT_SECRET: "${GITHUB_CLIENT_SECRET}" DISABLE_COLLECTSTATIC: 1 DOCKERIZED: 1 OPENAI_API_KEY: "${OPENAI_API_KEY}" GPT_ENGINE: "${GPT_ENGINE}" SECRET_KEY: "${HOME_SERVER_PLANE_SECRET_KEY}"
Essentially, both the frontend and backend are on
plane.example.com
. Since the frontend always appends/api
to any backend calls, we can make traefik forward requests toplane.example.com/api
to the backend and everything that's onplane.example.com
that is not under/api
to the frontend.I'm now struggling with getting plane to work with gmail 😄
All good? I'm Brazilian and I saw that you're from Portugal. Could you help me climb the plane with Traefik? I have good experience with docker but I'm having difficulty starting the "plane"
If you can help, you can contact me via WhatsApp 5587981148453