Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add template for erpnext #3882

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

barredterra
Copy link
Contributor

@barredterra barredterra commented Oct 11, 2024

I followed https://coolify.io/docs/contribute/service and modified https://github.com/frappe/frappe_docker/blob/main/pwd.yml to use coolify's generated variables.

Remaining issues:

  • the environment variable SERVICE_PASSWORD_CREATESITE doesn't automatically show up in the coolify UI (v4.0.0-beta.359), couldn't figure out why. After adding it manually, the deployment works.
    Edit, this works:

     command: bench new-site --admin-password=$$ADMIN_PASSWORD
     environment:
       - ADMIN_PASSWORD=${SERVICE_PASSWORD_CREATESITE}
  • the environment variable SERVICE_FQDN_FRONTEND does not contain a FQDN, as the name would imply, but an URL (including the protocol). -> went back to hardcoded sitename to avoid extracting FQDN from URL in all places

  • how do we handle upgrades?
    Update this file from time to time or use variables like image: ${ERPNEXT_IMAGE:-frappe/erpnext}:${ERPNEXT_VERSION_TAG:-latest}?

  • automatically run migration patches

  • Add health checks for all services:

  • Use coolify's proxy (Traefik)? https://coolify.io/docs/knowledge-base/docker/compose#labels

  • Write operator docs - any structured way available?

Note

I have little experience with coolify and docker, so all help is welcome.

@barredterra barredterra marked this pull request as draft October 11, 2024 14:53
@peaklabs-dev peaklabs-dev added the ⚙️ Service Issues requesting or PRs adding/fixing service templates. label Oct 11, 2024
@NagariaHussain
Copy link

Thanks for this @barredterra !

@revant
Copy link

revant commented Oct 14, 2024

https://github.com/frappe/frappe_docker/blob/main/pwd.yml to use coolify's generated variables.

This compose file is a simple set of containers required to run ERPNext. Two services out of these files are ideally run only once. There is a service to configure db and redis hosts for all services and another one is create-site to create the first site with list of apps mentioned in command.

Create site will not run multiple times, it will fail if it finds same site already created. The configure service will run on every restart. It can be kept as it is and use it to change hosts on restarts or a script can be added that check if config is already set and skips re-setting the config.

  • how do we handle upgrades? (Update this file from time to time? Is there a way to give more control to the user? Do we need to run patches? )

I've added following service in docker swarm stack. It never starts. It can be triggered via CI or webhook when you need to upgrade.

  migration:
    image: frappe/erpnext:${VERSION}
    deploy:
      restart_policy:
        condition: none
    entrypoint: ["bash", "-c"]
    command:
      - |
        bench --site all set-config -p maintenance_mode 1
        bench --site all set-config -p pause_scheduler 1
        bench --site all migrate
        bench --site all set-config -p maintenance_mode 0
        bench --site all set-config -p pause_scheduler 0
    volumes:
      - sites:/home/frappe/frappe-bench/sites

For this case it can have a script that checks if site is running on the versions of apps in image and only run the command if there is a diff in any version. This will ensure the bench --site all migrate never triggers on every restart, it will check version and only run if migration is needed.

the status still shows as "unhealthy", although everything seems to be running fine.

health checks are needed?

  • If possible just do curl -H "Host: ${SITENAME}" http://localhost:8080/api/method/ping for gunicorn
  • Or just do mysqladmin ping -h $(jq -r .db_host sites/common_site_config.json) -u $(jq -r .db_name sites/${SITENAME}/site_config.json) -p$(jq -r .db_password sites/${SITENAME}/site_config.json)

Use coolify's proxy (Traefik)? https://coolify.io/docs/knowledge-base/docker/compose#labels

For my traefik setup done like this

version: '3.7'

services:
  traefik:
    image: traefik:${TRAEFIK_VERSION:-v2.9}
    ports:
      - target: 80
        published: 80
        mode: host
      - target: 443
        published: 443
        mode: host
    deploy:
      placement:
        constraints:
          # Required for the TLS certificates
          - node.labels.traefik-public.traefik-public-certificates == true
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.constraint-label=traefik-public
        - traefik.http.middlewares.admin-auth.basicauth.users=admin:${HASHED_PASSWORD?Variable not set}
        - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
        - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
        - traefik.http.routers.traefik-public-http.rule=Host(`${TRAEFIK_DOMAIN?Variable not set}`)
        - traefik.http.routers.traefik-public-http.entrypoints=http
        - traefik.http.routers.traefik-public-http.middlewares=https-redirect
        - traefik.http.routers.traefik-public-https.rule=Host(`${TRAEFIK_DOMAIN}`)
        - traefik.http.routers.traefik-public-https.entrypoints=https
        - traefik.http.routers.traefik-public-https.tls=true
        - traefik.http.routers.traefik-public-https.service=api@internal
        - traefik.http.routers.traefik-public-https.tls.certresolver=le
        - traefik.http.routers.traefik-public-https.middlewares=admin-auth
        - traefik.http.services.traefik-public.loadbalancer.server.port=8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-public-certificates:/certificates
    command:
      - --providers.docker
      - --providers.docker.constraints=Label(`traefik.constraint-label`, `traefik-public`)
      - --providers.docker.exposedbydefault=false
      - --providers.docker.swarmmode
      - --entrypoints.http.address=:80
      - --entrypoints.https.address=:443
      - --certificatesresolvers.le.acme.email=${EMAIL?Variable not set}
      - --certificatesresolvers.le.acme.storage=/certificates/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
      - --accesslog
      - --log
      - --api
    networks:
      - traefik-public

volumes:
  traefik-public-certificates:

networks:
  traefik-public:
    name: traefik-public
    attachable: true

I use these labels:

version: "3.7"

services:
  frontend:
    image: frappe/erpnext:${VERSION}
    command:
      - nginx-entrypoint.sh
    deploy:
      restart_policy:
        condition: on-failure
      labels:
        - traefik.enable=true
        - traefik.docker.network=traefik-public
        - traefik.constraint-label=traefik-public
        - traefik.http.middlewares.prod-redirect.redirectscheme.scheme=https
        # Change router name prefix from erpnext to the name of stack in case of multi bench setup
        - traefik.http.routers.${BENCH_NAME:-erpnext}-http.rule=Host(${SITES:?No sites set})
        - traefik.http.routers.${BENCH_NAME:-erpnext}-http.entrypoints=http
        # Remove following lines in case of local setup
        - traefik.http.routers.${BENCH_NAME:-erpnext}-http.middlewares=prod-redirect
        - traefik.http.routers.${BENCH_NAME:-erpnext}-https.rule=Host(${SITES})
        - traefik.http.routers.${BENCH_NAME:-erpnext}-https.entrypoints=https
        - traefik.http.routers.${BENCH_NAME:-erpnext}-https.tls=true
        - traefik.http.routers.${BENCH_NAME:-erpnext}-https.tls.certresolver=le
        # Remove above lines in case of local setup
        # Uncomment and change domain for non-www to www redirect
        # - traefik.http.routers.${BENCH_NAME:-erpnext}-https.middlewares=nonwwwtowww
        # - traefik.http.middlewares.nonwwwtowww.redirectregex.regex=^https?://(?:www\.)?(.*)
        # - traefik.http.middlewares.nonwwwtowww.redirectregex.replacement=https://www.$$1
        - traefik.http.services.${BENCH_NAME:-erpnext}.loadbalancer.server.port=8080
    environment:
      BACKEND: backend:8000
      FRAPPE_SITE_NAME_HEADER: $$host
      SOCKETIO: websocket:9000
      UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
      UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
      UPSTREAM_REAL_IP_RECURSIVE: "off"
    volumes:
      - sites:/home/frappe/frappe-bench/sites

@revant
Copy link

revant commented Oct 14, 2024

If you need site backup done by frappe-bench then translate this to a scheduled task https://github.com/frappe/frappe_docker/blob/main/docs/backup-and-push-cronjob.md

@NagariaHussain
Copy link

Maybe we can add HR and Builder out of the box? (adds a lot of value)

@barredterra
Copy link
Contributor Author

Maybe we can add HR and Builder out of the box? (adds a

I think ideally we'd make the image configurable (with the current one being the default). This way everybody should be able to use their own set of apps.

@NagariaHussain
Copy link

Can you help here @revant ? I think this getting merged will be quite nice for the ERPNext ecosystem!

Let's get this done @barredterra !

@barredterra
Copy link
Contributor Author

@revant do you have an idea how we could set useful health checks for the websocket, queue workers and scheduler?

@marcoantonio123456
Copy link

Some observations from my experience using pwd.yml in Coolify:

  1. Shared variables with the docker image seem to work only during initial setup.
  2. While bench commands are essential for the Frappe framework, they can occasionally lead to volume corruption issues. Some additional documentation on handling and recovering from these situations would be really helpful.
  3. Since Traefik uses port 8080 by default, it might be worth considering changing the default port to 8081 to avoid conflicts.

Hope this feedback helps improve the setup process! 😊

@revant
Copy link

revant commented Nov 22, 2024

@revant do you have an idea how we could set useful health checks for the websocket, queue workers and scheduler?

Simple approach:

Complex approach:

  • for websocket, frontend and backend: curl -H "Host: {site-name}" http://localhost:8000/api/method/ping, Problem is, it cannot start without a site and only checks one site from the bench.
  • for workers can use ./env/bin/rq ... https://python-rq.org/docs/monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⚙️ Service Issues requesting or PRs adding/fixing service templates.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants