plane icon indicating copy to clipboard operation
plane copied to clipboard

[feature]: simplify the docker compose setup

Open Robin-Sch opened this issue 1 year ago • 10 comments

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.

Robin-Sch avatar May 06 '23 17:05 Robin-Sch

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

sturnclaw avatar May 07 '23 21:05 sturnclaw

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.

Robin-Sch avatar May 08 '23 16:05 Robin-Sch

+1 to this.

I'm using traefik as the proxy container, and am still struggling to get this working.

gokuu avatar May 08 '23 23:05 gokuu

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

Robin-Sch avatar May 09 '23 19:05 Robin-Sch

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 😄

gokuu avatar May 10 '23 08:05 gokuu

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. 🤷

gokuu avatar May 10 '23 21:05 gokuu

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.

require-dev avatar May 30 '23 09:05 require-dev

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

Robin-Sch avatar Jun 02 '23 12:06 Robin-Sch

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.

pablohashescobar avatar Jun 03 '23 05:06 pablohashescobar

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.

Robin-Sch avatar Jun 29 '23 19:06 Robin-Sch

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.

  1. Had to remove the platform property from services items where it existed.

  2. 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.

  1. Added to api service for my reverse proxy.
    ports:
      - 5589:8000
  1. 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

smcpeck avatar Jan 06 '24 17:01 smcpeck

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 avatar Jan 06 '24 19:01 Robin-Sch

@Robin-Sch you can use the docker-compose.yml which uses the images from docker hub. Closing this issue as it is resolved

pablohashescobar avatar Jan 20 '24 05:01 pablohashescobar

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 😄

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

JobasFernandes avatar Feb 29 '24 14:02 JobasFernandes