diff --git a/.dockerignore b/.dockerignore index 3e68f8ad..9f2a3517 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -README.md -LICENSE -.gitignore -compose*.yaml +README.md +LICENSE +.gitignore +compose*.yaml diff --git a/.editorconfig b/.editorconfig index 4f050ea6..dd8f6058 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,19 +1,19 @@ -# http://editorconfig.org -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_style = space -indent_size = 2 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.{py, pyi}] -indent_size = 4 - -[*Dockerfile*] -indent_size = 4 - -[*.md] -trim_trailing_whitespace = false +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{py, pyi}] +indent_size = 4 + +[*Dockerfile*] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d4c7a8c1..8e8c0259 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,34 +1,34 @@ ---- -name: Bug report -about: Report a bug encountered while using the Frappe_docker -labels: bug ---- - - - -## Description of the issue - -## Context information (for bug reports) - -## Steps to reproduce the issue - -1. -2. -3. - -### Observed result - -### Expected result - -### Stacktrace / full error message if available - -``` -(paste here) -``` +--- +name: Bug report +about: Report a bug encountered while using the Frappe_docker +labels: bug +--- + + + +## Description of the issue + +## Context information (for bug reports) + +## Steps to reproduce the issue + +1. +2. +3. + +### Observed result + +### Expected result + +### Stacktrace / full error message if available + +``` +(paste here) +``` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 15c410eb..d6dd1648 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,23 +1,23 @@ ---- -name: Feature request -about: Suggest an idea to improve frappe_docker -labels: enhancement ---- - - - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. +--- +name: Feature request +about: Suggest an idea to improve frappe_docker +labels: enhancement +--- + + + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/question-about-using-frappe_docker.md b/.github/ISSUE_TEMPLATE/question-about-using-frappe_docker.md index 6b4b9cbf..355c9aac 100644 --- a/.github/ISSUE_TEMPLATE/question-about-using-frappe_docker.md +++ b/.github/ISSUE_TEMPLATE/question-about-using-frappe_docker.md @@ -1,12 +1,12 @@ ---- -name: Question about using Frappe/Frappe Apps -about: Ask how to do something -labels: question ---- - - +--- +name: Question about using Frappe/Frappe Apps +about: Ask how to do something +labels: question +--- + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6c9fc532..abed0793 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ -> Please provide enough information so that others can review your pull request: - - - -> Explain the **details** for making this change. What existing problem does the pull request solve? - - +> Please provide enough information so that others can review your pull request: + + + +> Explain the **details** for making this change. What existing problem does the pull request solve? + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8fb9a8c8..3dd0a583 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,26 +1,26 @@ -version: 2 -updates: - - package-ecosystem: github-actions - directory: / - schedule: - interval: daily - - - package-ecosystem: docker - directory: images/bench - schedule: - interval: daily - - - package-ecosystem: docker - directory: images/production - schedule: - interval: daily - - - package-ecosystem: docker - directory: images/custom - schedule: - interval: daily - - - package-ecosystem: pip - directory: / - schedule: - interval: daily +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + + - package-ecosystem: docker + directory: images/bench + schedule: + interval: daily + + - package-ecosystem: docker + directory: images/production + schedule: + interval: daily + + - package-ecosystem: docker + directory: images/custom + schedule: + interval: daily + + - package-ecosystem: pip + directory: / + schedule: + interval: daily diff --git a/.github/scripts/get_latest_tags.py b/.github/scripts/get_latest_tags.py index 7d168bab..f6c80c1b 100644 --- a/.github/scripts/get_latest_tags.py +++ b/.github/scripts/get_latest_tags.py @@ -1,78 +1,78 @@ -from __future__ import annotations - -import argparse -import json -import os -import re -import subprocess -import sys -from typing import Literal - -Repo = Literal["frappe", "erpnext"] -MajorVersion = Literal["12", "13", "14", "15", "develop"] - - -def get_latest_tag(repo: Repo, version: MajorVersion) -> str: - if version == "develop": - return "develop" - regex = rf"v{version}.*" - refs = subprocess.check_output( - ( - "git", - "-c", - "versionsort.suffix=-", - "ls-remote", - "--refs", - "--tags", - "--sort=v:refname", - f"https://github.com/frappe/{repo}", - str(regex), - ), - encoding="UTF-8", - ).split()[1::2] - - if not refs: - raise RuntimeError(f'No tags found for version "{regex}"') - ref = refs[-1] - matches: list[str] = re.findall(regex, ref) - if not matches: - raise RuntimeError(f'Can\'t parse tag from ref "{ref}"') - return matches[0] - - -def update_env(file_name: str, frappe_tag: str, erpnext_tag: str | None = None): - text = f"\nFRAPPE_VERSION={frappe_tag}" - if erpnext_tag: - text += f"\nERPNEXT_VERSION={erpnext_tag}" - - with open(file_name, "a") as f: - f.write(text) - - -def _print_resp(frappe_tag: str, erpnext_tag: str | None = None): - print(json.dumps({"frappe": frappe_tag, "erpnext": erpnext_tag})) - - -def main(_args: list[str]) -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--repo", choices=["frappe", "erpnext"], required=True) - parser.add_argument( - "--version", choices=["12", "13", "14", "15", "develop"], required=True - ) - args = parser.parse_args(_args) - - frappe_tag = get_latest_tag("frappe", args.version) - if args.repo == "erpnext": - erpnext_tag = get_latest_tag("erpnext", args.version) - else: - erpnext_tag = None - - file_name = os.getenv("GITHUB_ENV") - if file_name: - update_env(file_name, frappe_tag, erpnext_tag) - _print_resp(frappe_tag, erpnext_tag) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) +from __future__ import annotations + +import argparse +import json +import os +import re +import subprocess +import sys +from typing import Literal + +Repo = Literal["frappe", "erpnext"] +MajorVersion = Literal["12", "13", "14", "15", "develop"] + + +def get_latest_tag(repo: Repo, version: MajorVersion) -> str: + if version == "develop": + return "develop" + regex = rf"v{version}.*" + refs = subprocess.check_output( + ( + "git", + "-c", + "versionsort.suffix=-", + "ls-remote", + "--refs", + "--tags", + "--sort=v:refname", + f"https://github.com/frappe/{repo}", + str(regex), + ), + encoding="UTF-8", + ).split()[1::2] + + if not refs: + raise RuntimeError(f'No tags found for version "{regex}"') + ref = refs[-1] + matches: list[str] = re.findall(regex, ref) + if not matches: + raise RuntimeError(f'Can\'t parse tag from ref "{ref}"') + return matches[0] + + +def update_env(file_name: str, frappe_tag: str, erpnext_tag: str | None = None): + text = f"\nFRAPPE_VERSION={frappe_tag}" + if erpnext_tag: + text += f"\nERPNEXT_VERSION={erpnext_tag}" + + with open(file_name, "a") as f: + f.write(text) + + +def _print_resp(frappe_tag: str, erpnext_tag: str | None = None): + print(json.dumps({"frappe": frappe_tag, "erpnext": erpnext_tag})) + + +def main(_args: list[str]) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--repo", choices=["frappe", "erpnext"], required=True) + parser.add_argument( + "--version", choices=["12", "13", "14", "15", "develop"], required=True + ) + args = parser.parse_args(_args) + + frappe_tag = get_latest_tag("frappe", args.version) + if args.repo == "erpnext": + erpnext_tag = get_latest_tag("erpnext", args.version) + else: + erpnext_tag = None + + file_name = os.getenv("GITHUB_ENV") + if file_name: + update_env(file_name, frappe_tag, erpnext_tag) + _print_resp(frappe_tag, erpnext_tag) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/.github/scripts/update_example_env.py b/.github/scripts/update_example_env.py index 8cbdea3a..4d05ed18 100644 --- a/.github/scripts/update_example_env.py +++ b/.github/scripts/update_example_env.py @@ -1,28 +1,28 @@ -import os -import re - - -def get_erpnext_version(): - erpnext_version = os.getenv("ERPNEXT_VERSION") - assert erpnext_version, "No ERPNext version set" - return erpnext_version - - -def update_env(erpnext_version: str): - with open("example.env", "r+") as f: - content = f.read() - content = re.sub( - rf"ERPNEXT_VERSION=.*", f"ERPNEXT_VERSION={erpnext_version}", content - ) - f.seek(0) - f.truncate() - f.write(content) - - -def main() -> int: - update_env(get_erpnext_version()) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) +import os +import re + + +def get_erpnext_version(): + erpnext_version = os.getenv("ERPNEXT_VERSION") + assert erpnext_version, "No ERPNext version set" + return erpnext_version + + +def update_env(erpnext_version: str): + with open("example.env", "r+") as f: + content = f.read() + content = re.sub( + rf"ERPNEXT_VERSION=.*", f"ERPNEXT_VERSION={erpnext_version}", content + ) + f.seek(0) + f.truncate() + f.write(content) + + +def main() -> int: + update_env(get_erpnext_version()) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/scripts/update_pwd.py b/.github/scripts/update_pwd.py index 998612c5..e0bb4d5a 100644 --- a/.github/scripts/update_pwd.py +++ b/.github/scripts/update_pwd.py @@ -1,30 +1,30 @@ -import os -import re - - -def get_versions(): - frappe_version = os.getenv("FRAPPE_VERSION") - erpnext_version = os.getenv("ERPNEXT_VERSION") - assert frappe_version, "No Frappe version set" - assert erpnext_version, "No ERPNext version set" - return frappe_version, erpnext_version - - -def update_pwd(frappe_version: str, erpnext_version: str): - with open("pwd.yml", "r+") as f: - content = f.read() - content = re.sub( - rf"frappe/erpnext:.*", f"frappe/erpnext:{erpnext_version}", content - ) - f.seek(0) - f.truncate() - f.write(content) - - -def main() -> int: - update_pwd(*get_versions()) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) +import os +import re + + +def get_versions(): + frappe_version = os.getenv("FRAPPE_VERSION") + erpnext_version = os.getenv("ERPNEXT_VERSION") + assert frappe_version, "No Frappe version set" + assert erpnext_version, "No ERPNext version set" + return frappe_version, erpnext_version + + +def update_pwd(frappe_version: str, erpnext_version: str): + with open("pwd.yml", "r+") as f: + content = f.read() + content = re.sub( + rf"frappe/erpnext:.*", f"frappe/erpnext:{erpnext_version}", content + ) + f.seek(0) + f.truncate() + f.write(content) + + +def main() -> int: + update_pwd(*get_versions()) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/workflows/build_bench.yml b/.github/workflows/build_bench.yml index 1835f1c3..2dc7f7be 100644 --- a/.github/workflows/build_bench.yml +++ b/.github/workflows/build_bench.yml @@ -1,59 +1,59 @@ -name: Bench - -on: - pull_request: - branches: - - main - paths: - - images/bench/** - - docker-bake.hcl - - .github/workflows/build_bench.yml - - schedule: - # Every day at 12:00 pm - - cron: 0 0 * * * - - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup QEMU - uses: docker/setup-qemu-action@v3 - with: - image: tonistiigi/binfmt:latest - platforms: all - - - name: Setup Buildx - uses: docker/setup-buildx-action@v3 - - - name: Set Environment Variables - run: cat example.env | grep -o '^[^#]*' >> "$GITHUB_ENV" - - - name: Get Bench Latest Version - run: echo "LATEST_BENCH_RELEASE=$(curl -s 'https://api.github.com/repos/frappe/bench/releases/latest' | jq -r '.tag_name')" >> "$GITHUB_ENV" - - - name: Build and test - uses: docker/bake-action@v6.9.0 - with: - source: . - targets: bench-test - - - name: Login - if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Push - if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - uses: docker/bake-action@v6.9.0 - with: - targets: bench - push: true - set: "*.platform=linux/amd64,linux/arm64" +name: Bench + +on: + pull_request: + branches: + - main + paths: + - images/bench/** + - docker-bake.hcl + - .github/workflows/build_bench.yml + + schedule: + # Every day at 12:00 pm + - cron: 0 0 * * * + + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + image: tonistiigi/binfmt:latest + platforms: all + + - name: Setup Buildx + uses: docker/setup-buildx-action@v3 + + - name: Set Environment Variables + run: cat example.env | grep -o '^[^#]*' >> "$GITHUB_ENV" + + - name: Get Bench Latest Version + run: echo "LATEST_BENCH_RELEASE=$(curl -s 'https://api.github.com/repos/frappe/bench/releases/latest' | jq -r '.tag_name')" >> "$GITHUB_ENV" + + - name: Build and test + uses: docker/bake-action@v6.9.0 + with: + source: . + targets: bench-test + + - name: Login + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Push + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + uses: docker/bake-action@v6.9.0 + with: + targets: bench + push: true + set: "*.platform=linux/amd64,linux/arm64" diff --git a/.github/workflows/build_develop.yml b/.github/workflows/build_develop.yml index 6103fd5f..f52489e0 100644 --- a/.github/workflows/build_develop.yml +++ b/.github/workflows/build_develop.yml @@ -1,33 +1,33 @@ -name: Develop build - -on: - pull_request: - branches: - - main - paths: - - images/production/** - - overrides/** - - tests/** - - compose.yaml - - docker-bake.hcl - - example.env - - .github/workflows/build_develop.yml - - schedule: - # Every day at 12:00 pm - - cron: 0 0 * * * - - workflow_dispatch: - -jobs: - build: - uses: ./.github/workflows/docker-build-push.yml - with: - repo: erpnext - version: develop - push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - python_version: 3.11.6 - node_version: 20.19.2 - secrets: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} +name: Develop build + +on: + pull_request: + branches: + - main + paths: + - images/production/** + - overrides/** + - tests/** + - compose.yaml + - docker-bake.hcl + - example.env + - .github/workflows/build_develop.yml + + schedule: + # Every day at 12:00 pm + - cron: 0 0 * * * + + workflow_dispatch: + +jobs: + build: + uses: ./.github/workflows/docker-build-push.yml + with: + repo: erpnext + version: develop + push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + python_version: 3.11.6 + node_version: 20.19.2 + secrets: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/build_stable.yml b/.github/workflows/build_stable.yml index 6ddbb0ea..d0af74e9 100644 --- a/.github/workflows/build_stable.yml +++ b/.github/workflows/build_stable.yml @@ -1,116 +1,116 @@ -name: Stable build - -on: - pull_request: - branches: - - main - paths: - - images/production/** - - overrides/** - - tests/** - - compose.yaml - - docker-bake.hcl - - example.env - - .github/workflows/build_stable.yml - - push: - branches: - - main - paths: - - images/production/** - - overrides/** - - tests/** - - compose.yaml - - docker-bake.hcl - - example.env - - # Triggered from frappe/frappe and frappe/erpnext on releases - repository_dispatch: - - workflow_dispatch: - -jobs: - v14: - uses: ./.github/workflows/docker-build-push.yml - with: - repo: erpnext - version: "14" - push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - python_version: 3.10.13 - node_version: 16.20.2 - secrets: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - - v15: - uses: ./.github/workflows/docker-build-push.yml - with: - repo: erpnext - version: "15" - push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - python_version: 3.11.6 - node_version: 20.19.2 - secrets: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - - update_versions: - name: Update example.env and pwd.yml - runs-on: ubuntu-latest - if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - needs: v15 - - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.10" - - - name: Get latest versions - run: python3 ./.github/scripts/get_latest_tags.py --repo erpnext --version 15 - - - name: Update - run: | - python3 ./.github/scripts/update_example_env.py - python3 ./.github/scripts/update_pwd.py - - - name: Push - run: | - git config --global user.name github-actions - git config --global user.email github-actions@github.com - git add example.env pwd.yml - if [ -z "$(git status --porcelain)" ]; then - echo "versions did not change, exiting." - exit 0 - else - echo "version changed, pushing changes..." - git commit -m "chore: Update example.env" - git pull --rebase - git push origin main - fi - - release_helm: - name: Release Helm - runs-on: ubuntu-latest - if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} - needs: v15 - - steps: - - name: Setup deploy key - uses: webfactory/ssh-agent@v0.9.1 - with: - ssh-private-key: ${{ secrets.HELM_DEPLOY_KEY }} - - - name: Setup Git Credentials - run: | - git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - - - name: Release - run: | - git clone git@github.com:frappe/helm.git && cd helm - pip install -r release_wizard/requirements.txt - ./release_wizard/wizard 15 patch --remote origin --ci +name: Stable build + +on: + pull_request: + branches: + - main + paths: + - images/production/** + - overrides/** + - tests/** + - compose.yaml + - docker-bake.hcl + - example.env + - .github/workflows/build_stable.yml + + push: + branches: + - main + paths: + - images/production/** + - overrides/** + - tests/** + - compose.yaml + - docker-bake.hcl + - example.env + + # Triggered from frappe/frappe and frappe/erpnext on releases + repository_dispatch: + + workflow_dispatch: + +jobs: + v14: + uses: ./.github/workflows/docker-build-push.yml + with: + repo: erpnext + version: "14" + push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + python_version: 3.10.13 + node_version: 16.20.2 + secrets: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + + v15: + uses: ./.github/workflows/docker-build-push.yml + with: + repo: erpnext + version: "15" + push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + python_version: 3.11.6 + node_version: 20.19.2 + secrets: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + + update_versions: + name: Update example.env and pwd.yml + runs-on: ubuntu-latest + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + needs: v15 + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - name: Get latest versions + run: python3 ./.github/scripts/get_latest_tags.py --repo erpnext --version 15 + + - name: Update + run: | + python3 ./.github/scripts/update_example_env.py + python3 ./.github/scripts/update_pwd.py + + - name: Push + run: | + git config --global user.name github-actions + git config --global user.email github-actions@github.com + git add example.env pwd.yml + if [ -z "$(git status --porcelain)" ]; then + echo "versions did not change, exiting." + exit 0 + else + echo "version changed, pushing changes..." + git commit -m "chore: Update example.env" + git pull --rebase + git push origin main + fi + + release_helm: + name: Release Helm + runs-on: ubuntu-latest + if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }} + needs: v15 + + steps: + - name: Setup deploy key + uses: webfactory/ssh-agent@v0.9.1 + with: + ssh-private-key: ${{ secrets.HELM_DEPLOY_KEY }} + + - name: Setup Git Credentials + run: | + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + + - name: Release + run: | + git clone git@github.com:frappe/helm.git && cd helm + pip install -r release_wizard/requirements.txt + ./release_wizard/wizard 15 patch --remote origin --ci diff --git a/.github/workflows/docker-build-push.yml b/.github/workflows/docker-build-push.yml index d17dbf9e..11e6a11d 100644 --- a/.github/workflows/docker-build-push.yml +++ b/.github/workflows/docker-build-push.yml @@ -1,101 +1,101 @@ -name: Build - -on: - workflow_call: - inputs: - repo: - required: true - type: string - description: "'erpnext' or 'frappe'" - version: - required: true - type: string - description: "Major version, git tags should match 'v{version}.*'; or 'develop'" - push: - required: true - type: boolean - python_version: - required: true - type: string - description: Python Version - node_version: - required: true - type: string - description: NodeJS Version - secrets: - DOCKERHUB_USERNAME: - required: true - DOCKERHUB_TOKEN: - required: true - -jobs: - build: - name: Build - runs-on: ubuntu-latest - services: - registry: - image: docker.io/registry:2 - ports: - - 5000:5000 - strategy: - matrix: - arch: [amd64, arm64] - - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup QEMU - uses: docker/setup-qemu-action@v3 - with: - image: tonistiigi/binfmt:latest - platforms: all - - - name: Setup Buildx - uses: docker/setup-buildx-action@v3 - with: - driver-opts: network=host - platforms: linux/${{ matrix.arch }} - - - name: Get latest versions - run: python3 ./.github/scripts/get_latest_tags.py --repo ${{ inputs.repo }} --version ${{ inputs.version }} - - - name: Set build args - run: | - echo "PYTHON_VERSION=${{ inputs.python_version }}" >> "$GITHUB_ENV" - echo "NODE_VERSION=${{ inputs.node_version }}" >> "$GITHUB_ENV" - - - name: Build - uses: docker/bake-action@v6.9.0 - with: - source: . - push: true - env: - REGISTRY_USER: localhost:5000/frappe - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.10" - - - name: Install dependencies - run: | - python -m venv venv - venv/bin/pip install -r requirements-test.txt - - - name: Test - run: venv/bin/pytest --color=yes - - - name: Login - if: ${{ inputs.push }} - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Push - if: ${{ inputs.push }} - uses: docker/bake-action@v6.9.0 - with: - push: true - set: "*.platform=linux/amd64,linux/arm64" +name: Build + +on: + workflow_call: + inputs: + repo: + required: true + type: string + description: "'erpnext' or 'frappe'" + version: + required: true + type: string + description: "Major version, git tags should match 'v{version}.*'; or 'develop'" + push: + required: true + type: boolean + python_version: + required: true + type: string + description: Python Version + node_version: + required: true + type: string + description: NodeJS Version + secrets: + DOCKERHUB_USERNAME: + required: true + DOCKERHUB_TOKEN: + required: true + +jobs: + build: + name: Build + runs-on: ubuntu-latest + services: + registry: + image: docker.io/registry:2 + ports: + - 5000:5000 + strategy: + matrix: + arch: [amd64, arm64] + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + image: tonistiigi/binfmt:latest + platforms: all + + - name: Setup Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + platforms: linux/${{ matrix.arch }} + + - name: Get latest versions + run: python3 ./.github/scripts/get_latest_tags.py --repo ${{ inputs.repo }} --version ${{ inputs.version }} + + - name: Set build args + run: | + echo "PYTHON_VERSION=${{ inputs.python_version }}" >> "$GITHUB_ENV" + echo "NODE_VERSION=${{ inputs.node_version }}" >> "$GITHUB_ENV" + + - name: Build + uses: docker/bake-action@v6.9.0 + with: + source: . + push: true + env: + REGISTRY_USER: localhost:5000/frappe + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m venv venv + venv/bin/pip install -r requirements-test.txt + + - name: Test + run: venv/bin/pytest --color=yes + + - name: Login + if: ${{ inputs.push }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Push + if: ${{ inputs.push }} + uses: docker/bake-action@v6.9.0 + with: + push: true + set: "*.platform=linux/amd64,linux/arm64" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6f72e86b..ec0db6bc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,35 +1,35 @@ -name: Lint - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.10.6" - - # For shfmt pre-commit hook - - name: Setup Go - uses: actions/setup-go@v6 - with: - go-version: "^1.14" - - - name: Install pre-commit - run: pip install -U pre-commit - - - name: Lint - run: pre-commit run --color=always --all-files - env: - GO111MODULE: on +name: Lint + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.10.6" + + # For shfmt pre-commit hook + - name: Setup Go + uses: actions/setup-go@v6 + with: + go-version: "^1.14" + + - name: Install pre-commit + run: pip install -U pre-commit + + - name: Lint + run: pre-commit run --color=always --all-files + env: + GO111MODULE: on diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml index 29a5b5af..88d27201 100644 --- a/.github/workflows/pre-commit-autoupdate.yml +++ b/.github/workflows/pre-commit-autoupdate.yml @@ -1,26 +1,26 @@ -name: Autoupdate pre-commit hooks - -on: - schedule: - # Every day at 7 am - - cron: 0 7 * * * - -jobs: - pre-commit-autoupdate: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Update pre-commit hooks - uses: vrslev/pre-commit-autoupdate@v1.0.0 - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v7 - with: - branch: pre-commit-autoupdate - title: "chore(deps): Update pre-commit hooks" - commit-message: "chore(deps): Update pre-commit hooks" - body: Update pre-commit hooks - labels: dependencies,development - delete-branch: True +name: Autoupdate pre-commit hooks + +on: + schedule: + # Every day at 7 am + - cron: 0 7 * * * + +jobs: + pre-commit-autoupdate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Update pre-commit hooks + uses: vrslev/pre-commit-autoupdate@v1.0.0 + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + branch: pre-commit-autoupdate + title: "chore(deps): Update pre-commit hooks" + commit-message: "chore(deps): Update pre-commit hooks" + body: Update pre-commit hooks + labels: dependencies,development + delete-branch: True diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 8849bfea..21156b55 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,18 +1,18 @@ -name: Mark stale issues and pull requests - -on: - schedule: - # Every day at 12:00 pm - - cron: 0 0 * * * - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v10 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: This issue has been automatically marked as stale. You have a week to explain why you believe this is an error. - stale-pr-message: This PR has been automatically marked as stale. You have a week to explain why you believe this is an error. - stale-issue-label: no-issue-activity - stale-pr-label: no-pr-activity +name: Mark stale issues and pull requests + +on: + schedule: + # Every day at 12:00 pm + - cron: 0 0 * * * + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: This issue has been automatically marked as stale. You have a week to explain why you believe this is an error. + stale-pr-message: This PR has been automatically marked as stale. You have a week to explain why you believe this is an error. + stale-issue-label: no-issue-activity + stale-pr-label: no-pr-activity diff --git a/.gitignore b/.gitignore index 94a9fe2f..477267f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,30 @@ -# Environment Variables -.env - -# mounted volume -sites - -development/* -!development/README.md -!development/installer.py -!development/apps-example.json -!development/vscode-example/ - -# Pycharm -.idea - -# VS Code -.vscode/** -!.vscode/extensions.json - -# VS Code devcontainer -.devcontainer -*.code-workspace - -# Python -*.pyc -__pycache__ -venv - -# NodeJS -node_modules +# Environment Variables +.env + +# mounted volume +sites + +development/* +!development/README.md +!development/installer.py +!development/apps-example.json +!development/vscode-example/ + +# Pycharm +.idea + +# VS Code +.vscode/** +!.vscode/extensions.json + +# VS Code devcontainer +.devcontainer +*.code-workspace + +# Python +*.pyc +__pycache__ +venv + +# NodeJS +node_modules diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a1d6036..0dec5773 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,55 +1,55 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: check-executables-have-shebangs - - id: check-shebang-scripts-are-executable - - id: trailing-whitespace - - id: end-of-file-fixer - - - repo: https://github.com/asottile/pyupgrade - rev: v3.19.1 - hooks: - - id: pyupgrade - args: [--py37-plus] - - - repo: https://github.com/psf/black - rev: 25.1.0 - hooks: - - id: black - - - repo: https://github.com/pycqa/isort - rev: 6.0.1 - hooks: - - id: isort - - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 - hooks: - - id: prettier - additional_dependencies: - - prettier@3.5.2 - - - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 - hooks: - - id: codespell - args: - - -L - - "ro" - - - repo: local - hooks: - - id: shfmt - name: shfmt - language: golang - additional_dependencies: [mvdan.cc/sh/v3/cmd/shfmt@latest] - entry: shfmt - args: [-w] - types: [shell] - - - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.10.0.1 - hooks: - - id: shellcheck - args: [-x] +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: trailing-whitespace + - id: end-of-file-fixer + + - repo: https://github.com/asottile/pyupgrade + rev: v3.19.1 + hooks: + - id: pyupgrade + args: [--py37-plus] + + - repo: https://github.com/psf/black + rev: 25.1.0 + hooks: + - id: black + + - repo: https://github.com/pycqa/isort + rev: 6.0.1 + hooks: + - id: isort + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + additional_dependencies: + - prettier@3.5.2 + + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + args: + - -L + - "ro" + + - repo: local + hooks: + - id: shfmt + name: shfmt + language: golang + additional_dependencies: [mvdan.cc/sh/v3/cmd/shfmt@latest] + entry: shfmt + args: [-w] + types: [shell] + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + args: [-x] diff --git a/.shellcheckrc b/.shellcheckrc index 8226afb6..bedcd0ed 100644 --- a/.shellcheckrc +++ b/.shellcheckrc @@ -1 +1 @@ -external-sources=true +external-sources=true diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 6151dbb7..9a46bcb6 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,9 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - - // List of extensions which should be recommended for users of this workspace. - "recommendations": ["ms-vscode-remote.remote-containers"], - // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [] -} +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + + // List of extensions which should be recommended for users of this workspace. + "recommendations": ["ms-vscode-remote.remote-containers"], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 0e01dd85..0216955f 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,76 +1,76 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socioeconomic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery and unwelcome sexual attention or - advances -- Trolling, insulting/derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or electronic - address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at hello@frappe.io. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socioeconomic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at hello@frappe.io. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3cc671ee..2ce19b89 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,81 +1,81 @@ -# Contribution Guidelines - -Before publishing a PR, please test builds locally. - -On each PR that contains changes relevant to Docker builds, images are being built and tested in our CI (GitHub Actions). - -> :evergreen_tree: Please be considerate when pushing commits and opening PR for multiple branches, as the process of building images uses energy and contributes to global warming. - -## Lint - -We use `pre-commit` framework to lint the codebase before committing. -First, you need to install pre-commit with pip: - -```shell -pip install pre-commit -``` - -Also you can use brew if you're on Mac: - -```shell -brew install pre-commit -``` - -To setup _pre-commit_ hook, run: - -```shell -pre-commit install -``` - -To run all the files in repository, run: - -```shell -pre-commit run --all-files -``` - -## Build - -We use [Docker Buildx Bake](https://docs.docker.com/engine/reference/commandline/buildx_bake/). To build the images, run command below: - -```shell -FRAPPE_VERSION=... ERPNEXT_VERSION=... docker buildx bake -``` - -Available targets can be found in `docker-bake.hcl`. - -## Test - -We use [pytest](https://pytest.org) for our integration tests. - -Install Python test requirements: - -```shell -python3 -m venv venv -source venv/bin/activate -pip install -r requirements-test.txt -``` - -Run pytest: - -```shell -pytest -``` - -# Documentation - -Place relevant markdown files in the `docs` directory and index them in README.md located at the root of repo. - -# Frappe and ERPNext updates - -Each Frappe/ERPNext release triggers new stable images builds as well as bump to helm chart. - -# Maintenance - -In case of new release of Debian. e.g. bullseye to bookworm. Change following files: - -- `images/erpnext/Containerfile` and `images/custom/Containerfile`: Change the files to use new debian release, make sure new python version tag that is available on new debian release image. e.g. 3.9.9 (bullseye) to 3.9.17 (bookworm) or 3.10.5 (bullseye) to 3.10.12 (bookworm). Make sure apt-get packages and wkhtmltopdf version are also upgraded accordingly. -- `images/bench/Dockerfile`: Change the files to use new debian release. Make sure apt-get packages and wkhtmltopdf version are also upgraded accordingly. - -Change following files on release of ERPNext - -- `.github/workflows/build_stable.yml`: Add the new release step under `jobs` and remove the unmaintained one. e.g. In case v12, v13 available, v14 will be added and v12 will be removed on release of v14. Also change the `needs:` for later steps to `v14` from `v13`. +# Contribution Guidelines + +Before publishing a PR, please test builds locally. + +On each PR that contains changes relevant to Docker builds, images are being built and tested in our CI (GitHub Actions). + +> :evergreen_tree: Please be considerate when pushing commits and opening PR for multiple branches, as the process of building images uses energy and contributes to global warming. + +## Lint + +We use `pre-commit` framework to lint the codebase before committing. +First, you need to install pre-commit with pip: + +```shell +pip install pre-commit +``` + +Also you can use brew if you're on Mac: + +```shell +brew install pre-commit +``` + +To setup _pre-commit_ hook, run: + +```shell +pre-commit install +``` + +To run all the files in repository, run: + +```shell +pre-commit run --all-files +``` + +## Build + +We use [Docker Buildx Bake](https://docs.docker.com/engine/reference/commandline/buildx_bake/). To build the images, run command below: + +```shell +FRAPPE_VERSION=... ERPNEXT_VERSION=... docker buildx bake +``` + +Available targets can be found in `docker-bake.hcl`. + +## Test + +We use [pytest](https://pytest.org) for our integration tests. + +Install Python test requirements: + +```shell +python3 -m venv venv +source venv/bin/activate +pip install -r requirements-test.txt +``` + +Run pytest: + +```shell +pytest +``` + +# Documentation + +Place relevant markdown files in the `docs` directory and index them in README.md located at the root of repo. + +# Frappe and ERPNext updates + +Each Frappe/ERPNext release triggers new stable images builds as well as bump to helm chart. + +# Maintenance + +In case of new release of Debian. e.g. bullseye to bookworm. Change following files: + +- `images/erpnext/Containerfile` and `images/custom/Containerfile`: Change the files to use new debian release, make sure new python version tag that is available on new debian release image. e.g. 3.9.9 (bullseye) to 3.9.17 (bookworm) or 3.10.5 (bullseye) to 3.10.12 (bookworm). Make sure apt-get packages and wkhtmltopdf version are also upgraded accordingly. +- `images/bench/Dockerfile`: Change the files to use new debian release. Make sure apt-get packages and wkhtmltopdf version are also upgraded accordingly. + +Change following files on release of ERPNext + +- `.github/workflows/build_stable.yml`: Add the new release step under `jobs` and remove the unmaintained one. e.g. In case v12, v13 available, v14 will be added and v12 will be removed on release of v14. Also change the `needs:` for later steps to `v14` from `v13`. diff --git a/LICENSE b/LICENSE index c4e7b3dc..fe3ddc60 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2017 Frappe Technologies Pvt. Ltd. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2017 Frappe Technologies Pvt. Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 6a208926..55bb4ba6 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,93 @@ -[![Build Stable](https://github.com/frappe/frappe_docker/actions/workflows/build_stable.yml/badge.svg)](https://github.com/frappe/frappe_docker/actions/workflows/build_stable.yml) -[![Build Develop](https://github.com/frappe/frappe_docker/actions/workflows/build_develop.yml/badge.svg)](https://github.com/frappe/frappe_docker/actions/workflows/build_develop.yml) - -Everything about [Frappe](https://github.com/frappe/frappe) and [ERPNext](https://github.com/frappe/erpnext) in containers. - -# Getting Started - -**New to Frappe Docker?** Read the [Getting Started Guide](docs/getting-started.md) for a comprehensive overview of repository structure, development workflow, custom apps, Docker concepts, and quick start examples. - -To get started you need [Docker](https://docs.docker.com/get-docker/), [docker-compose](https://docs.docker.com/compose/), and [git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git) setup on your machine. For Docker basics and best practices refer to Docker's [documentation](http://docs.docker.com). - -Once completed, chose one of the following two sections for next steps. - -### Try in Play With Docker - -To play in an already set up sandbox, in your browser, click the button below: - - - Try in PWD - - -### Try on your Dev environment - -First clone the repo: - -```sh -git clone https://github.com/frappe/frappe_docker -cd frappe_docker -``` - -Then run: `docker compose -f pwd.yml up -d` - -### To run on ARM64 architecture follow this instructions - -After you clone the repo and `cd frappe_docker`, run this command to build multi-architecture images specifically for ARM64. - -`docker buildx bake --no-cache --set "*.platform=linux/arm64"` - -and then - -- add `platform: linux/arm64` to all services in the `pwd.yml` -- replace the current specified versions of erpnext image on `pwd.yml` with `:latest` - -Then run: `docker compose -f pwd.yml up -d` - -## Final steps - -Wait for 5 minutes for ERPNext site to be created or check `create-site` container logs before opening browser on port 8080. (username: `Administrator`, password: `admin`) - -If you ran in a Dev Docker environment, to view container logs: `docker compose -f pwd.yml logs -f create-site`. Don't worry about some of the initial error messages, some services take a while to become ready, and then they go away. - -# Documentation - -### [Getting Started Guide](docs/getting-started.md) - -### [Frequently Asked Questions](https://github.com/frappe/frappe_docker/wiki/Frequently-Asked-Questions) - -### [Production](#production) - -- [List of containers](docs/container-setup/01-overview.md) -- [Single Compose Setup](docs/single-compose-setup.md) -- [Environment Variables](docs/container-setup/env-variables.md) -- [Single Server Example](docs/single-server-example.md) -- [Setup Options](docs/setup-options.md) -- [Site Operations](docs/site-operations.md) -- [Backup and Push Cron Job](docs/backup-and-push-cronjob.md) -- [Port Based Multi Tenancy](docs/port-based-multi-tenancy.md) -- [Migrate from multi-image setup](docs/migrate-from-multi-image-setup.md) -- [running on linux/mac](docs/setup_for_linux_mac.md) -- [TLS for local deployment](docs/tls-for-local-deployment.md) - -### [Custom Images](#custom-images) - -- [Custom Apps](docs/container-setup/02-build-setup.md) -- [Build Version 10 Images](docs/build-version-10-images.md) - -### [Development](#development) - -- [Development using containers](docs/development.md) -- [Bench Console and VSCode Debugger](docs/bench-console-and-vscode-debugger.md) -- [Connect to localhost services](docs/connect-to-localhost-services-from-containers-for-local-app-development.md) - -### [Troubleshoot](docs/troubleshoot.md) - -# Contributing - -If you want to contribute to this repo refer to [CONTRIBUTING.md](CONTRIBUTING.md) - -This repository is only for container related stuff. You also might want to contribute to: - -- [Frappe framework](https://github.com/frappe/frappe#contributing), -- [ERPNext](https://github.com/frappe/erpnext#contributing), -- [Frappe Bench](https://github.com/frappe/bench). +[![Build Stable](https://github.com/frappe/frappe_docker/actions/workflows/build_stable.yml/badge.svg)](https://github.com/frappe/frappe_docker/actions/workflows/build_stable.yml) +[![Build Develop](https://github.com/frappe/frappe_docker/actions/workflows/build_develop.yml/badge.svg)](https://github.com/frappe/frappe_docker/actions/workflows/build_develop.yml) + +Everything about [Frappe](https://github.com/frappe/frappe) and [ERPNext](https://github.com/frappe/erpnext) in containers. + +# Getting Started + +**New to Frappe Docker?** Read the [Getting Started Guide](docs/getting-started.md) for a comprehensive overview of repository structure, development workflow, custom apps, Docker concepts, and quick start examples. + +To get started you need [Docker](https://docs.docker.com/get-docker/), [docker-compose](https://docs.docker.com/compose/), and [git](https://docs.github.com/en/get-started/getting-started-with-git/set-up-git) setup on your machine. For Docker basics and best practices refer to Docker's [documentation](http://docs.docker.com). + +Once completed, chose one of the following two sections for next steps. + +### Try in Play With Docker + +To play in an already set up sandbox, in your browser, click the button below: + + + Try in PWD + + +### Try on your Dev environment + +First clone the repo: + +```sh +git clone https://github.com/frappe/frappe_docker +cd frappe_docker +``` + +Then run: `docker compose -f pwd.yml up -d` + +### To run on ARM64 architecture follow this instructions + +After you clone the repo and `cd frappe_docker`, run this command to build multi-architecture images specifically for ARM64. + +`docker buildx bake --no-cache --set "*.platform=linux/arm64"` + +and then + +- add `platform: linux/arm64` to all services in the `pwd.yml` +- replace the current specified versions of erpnext image on `pwd.yml` with `:latest` + +Then run: `docker compose -f pwd.yml up -d` + +## Final steps + +Wait for 5 minutes for ERPNext site to be created or check `create-site` container logs before opening browser on port 8080. (username: `Administrator`, password: `admin`) + +If you ran in a Dev Docker environment, to view container logs: `docker compose -f pwd.yml logs -f create-site`. Don't worry about some of the initial error messages, some services take a while to become ready, and then they go away. + +# Documentation + +### [Getting Started Guide](docs/getting-started.md) + +### [Frequently Asked Questions](https://github.com/frappe/frappe_docker/wiki/Frequently-Asked-Questions) + +### [Production](#production) + +- [List of containers](docs/container-setup/01-overview.md) +- [Single Compose Setup](docs/single-compose-setup.md) +- [Environment Variables](docs/container-setup/env-variables.md) +- [Single Server Example](docs/single-server-example.md) +- [Setup Options](docs/setup-options.md) +- [Site Operations](docs/site-operations.md) +- [Backup and Push Cron Job](docs/backup-and-push-cronjob.md) +- [Port Based Multi Tenancy](docs/port-based-multi-tenancy.md) +- [Migrate from multi-image setup](docs/migrate-from-multi-image-setup.md) +- [running on linux/mac](docs/setup_for_linux_mac.md) +- [TLS for local deployment](docs/tls-for-local-deployment.md) + +### [Custom Images](#custom-images) + +- [Custom Apps](docs/container-setup/02-build-setup.md) +- [Build Version 10 Images](docs/build-version-10-images.md) + +### [Development](#development) + +- [Development using containers](docs/development.md) +- [Bench Console and VSCode Debugger](docs/bench-console-and-vscode-debugger.md) +- [Connect to localhost services](docs/connect-to-localhost-services-from-containers-for-local-app-development.md) + +### [Troubleshoot](docs/troubleshoot.md) + +# Contributing + +If you want to contribute to this repo refer to [CONTRIBUTING.md](CONTRIBUTING.md) + +This repository is only for container related stuff. You also might want to contribute to: + +- [Frappe framework](https://github.com/frappe/frappe#contributing), +- [ERPNext](https://github.com/frappe/erpnext#contributing), +- [Frappe Bench](https://github.com/frappe/bench). diff --git a/compose.yaml b/compose.yaml index edf406ca..946dae67 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,95 +1,95 @@ -x-customizable-image: &customizable_image - # By default the image used only contains the `frappe` and `erpnext` apps. - # See https://github.com/frappe/frappe_docker/blob/main/docs/container-setup/02-build-setup.md#define-custom-apps - # about using custom images. - image: ${CUSTOM_IMAGE:-frappe/erpnext}:${CUSTOM_TAG:-$ERPNEXT_VERSION} - pull_policy: ${PULL_POLICY:-always} - restart: ${RESTART_POLICY:-unless-stopped} - -x-depends-on-configurator: &depends_on_configurator - depends_on: - configurator: - condition: service_completed_successfully - -x-backend-defaults: &backend_defaults - <<: [*depends_on_configurator, *customizable_image] - volumes: - - sites:/home/frappe/frappe-bench/sites - -services: - configurator: - <<: *backend_defaults - platform: linux/amd64 - entrypoint: - - bash - - -c - # add redis_socketio for backward compatibility - command: - - > - ls -1 apps > sites/apps.txt; - bench set-config -g db_host $$DB_HOST; - bench set-config -gp db_port $$DB_PORT; - bench set-config -g redis_cache "redis://$$REDIS_CACHE"; - bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; - bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; - bench set-config -gp socketio_port $$SOCKETIO_PORT; - environment: - DB_HOST: ${DB_HOST:-} - DB_PORT: ${DB_PORT:-} - REDIS_CACHE: ${REDIS_CACHE:-} - REDIS_QUEUE: ${REDIS_QUEUE:-} - SOCKETIO_PORT: 9000 - depends_on: {} - restart: on-failure - - backend: - <<: *backend_defaults - platform: linux/amd64 - - frontend: - <<: *customizable_image - platform: linux/amd64 - command: - - nginx-entrypoint.sh - environment: - BACKEND: backend:8000 - SOCKETIO: websocket:9000 - FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host} - UPSTREAM_REAL_IP_ADDRESS: ${UPSTREAM_REAL_IP_ADDRESS:-127.0.0.1} - UPSTREAM_REAL_IP_HEADER: ${UPSTREAM_REAL_IP_HEADER:-X-Forwarded-For} - UPSTREAM_REAL_IP_RECURSIVE: ${UPSTREAM_REAL_IP_RECURSIVE:-off} - PROXY_READ_TIMEOUT: ${PROXY_READ_TIMEOUT:-120} - CLIENT_MAX_BODY_SIZE: ${CLIENT_MAX_BODY_SIZE:-50m} - volumes: - - sites:/home/frappe/frappe-bench/sites - depends_on: - - backend - - websocket - - websocket: - <<: [*depends_on_configurator, *customizable_image] - platform: linux/amd64 - command: - - node - - /home/frappe/frappe-bench/apps/frappe/socketio.js - volumes: - - sites:/home/frappe/frappe-bench/sites - - queue-short: - <<: *backend_defaults - platform: linux/amd64 - command: bench worker --queue short,default - - queue-long: - <<: *backend_defaults - platform: linux/amd64 - command: bench worker --queue long,default,short - - scheduler: - <<: *backend_defaults - platform: linux/amd64 - command: bench schedule - -# ERPNext requires local assets access (Frappe does not) -volumes: - sites: +x-customizable-image: &customizable_image + # By default the image used only contains the `frappe` and `erpnext` apps. + # See https://github.com/frappe/frappe_docker/blob/main/docs/container-setup/02-build-setup.md#define-custom-apps + # about using custom images. + image: ${CUSTOM_IMAGE:-frappe/erpnext}:${CUSTOM_TAG:-$ERPNEXT_VERSION} + pull_policy: ${PULL_POLICY:-always} + restart: ${RESTART_POLICY:-unless-stopped} + +x-depends-on-configurator: &depends_on_configurator + depends_on: + configurator: + condition: service_completed_successfully + +x-backend-defaults: &backend_defaults + <<: [*depends_on_configurator, *customizable_image] + volumes: + - sites:/home/frappe/frappe-bench/sites + +services: + configurator: + <<: *backend_defaults + platform: linux/amd64 + entrypoint: + - bash + - -c + # add redis_socketio for backward compatibility + command: + - > + ls -1 apps > sites/apps.txt; + bench set-config -g db_host $$DB_HOST; + bench set-config -gp db_port $$DB_PORT; + bench set-config -g redis_cache "redis://$$REDIS_CACHE"; + bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; + bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; + bench set-config -gp socketio_port $$SOCKETIO_PORT; + environment: + DB_HOST: ${DB_HOST:-} + DB_PORT: ${DB_PORT:-} + REDIS_CACHE: ${REDIS_CACHE:-} + REDIS_QUEUE: ${REDIS_QUEUE:-} + SOCKETIO_PORT: 9000 + depends_on: {} + restart: on-failure + + backend: + <<: *backend_defaults + platform: linux/amd64 + + frontend: + <<: *customizable_image + platform: linux/amd64 + command: + - nginx-entrypoint.sh + environment: + BACKEND: backend:8000 + SOCKETIO: websocket:9000 + FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host} + UPSTREAM_REAL_IP_ADDRESS: ${UPSTREAM_REAL_IP_ADDRESS:-127.0.0.1} + UPSTREAM_REAL_IP_HEADER: ${UPSTREAM_REAL_IP_HEADER:-X-Forwarded-For} + UPSTREAM_REAL_IP_RECURSIVE: ${UPSTREAM_REAL_IP_RECURSIVE:-off} + PROXY_READ_TIMEOUT: ${PROXY_READ_TIMEOUT:-120} + CLIENT_MAX_BODY_SIZE: ${CLIENT_MAX_BODY_SIZE:-50m} + volumes: + - sites:/home/frappe/frappe-bench/sites + depends_on: + - backend + - websocket + + websocket: + <<: [*depends_on_configurator, *customizable_image] + platform: linux/amd64 + command: + - node + - /home/frappe/frappe-bench/apps/frappe/socketio.js + volumes: + - sites:/home/frappe/frappe-bench/sites + + queue-short: + <<: *backend_defaults + platform: linux/amd64 + command: bench worker --queue short,default + + queue-long: + <<: *backend_defaults + platform: linux/amd64 + command: bench worker --queue long,default,short + + scheduler: + <<: *backend_defaults + platform: linux/amd64 + command: bench schedule + +# ERPNext requires local assets access (Frappe does not) +volumes: + sites: diff --git a/devcontainer-example/devcontainer.json b/devcontainer-example/devcontainer.json index 2e60f65d..1f7f6218 100644 --- a/devcontainer-example/devcontainer.json +++ b/devcontainer-example/devcontainer.json @@ -1,32 +1,32 @@ -{ - "name": "Frappe Bench", - "forwardPorts": [8000, 9000, 6787], - "remoteUser": "frappe", - "customizations": { - "vscode": { - "extensions": [ - "ms-python.python", - "ms-vscode.live-server", - "grapecity.gc-excelviewer", - "mtxr.sqltools", - "visualstudioexptteam.vscodeintellicode" - ], - "settings": { - "terminal.integrated.profiles.linux": { - "frappe bash": { - "path": "/bin/bash" - } - }, - "terminal.integrated.defaultProfile.linux": "frappe bash", - "debug.node.autoAttach": "disabled" - } - } - }, - "dockerComposeFile": "./docker-compose.yml", - "service": "frappe", - "workspaceFolder": "/workspace/development", - "shutdownAction": "stopCompose", - "mounts": [ - "source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/frappe/.ssh,type=bind,consistency=cached" - ] -} +{ + "name": "Frappe Bench", + "forwardPorts": [8000, 9000, 6787], + "remoteUser": "frappe", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-vscode.live-server", + "grapecity.gc-excelviewer", + "mtxr.sqltools", + "visualstudioexptteam.vscodeintellicode" + ], + "settings": { + "terminal.integrated.profiles.linux": { + "frappe bash": { + "path": "/bin/bash" + } + }, + "terminal.integrated.defaultProfile.linux": "frappe bash", + "debug.node.autoAttach": "disabled" + } + } + }, + "dockerComposeFile": "./docker-compose.yml", + "service": "frappe", + "workspaceFolder": "/workspace/development", + "shutdownAction": "stopCompose", + "mounts": [ + "source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/home/frappe/.ssh,type=bind,consistency=cached" + ] +} diff --git a/devcontainer-example/docker-compose.yml b/devcontainer-example/docker-compose.yml index 0d6d9d8a..45407878 100644 --- a/devcontainer-example/docker-compose.yml +++ b/devcontainer-example/docker-compose.yml @@ -1,90 +1,90 @@ -version: "3.7" -services: - mariadb: - image: docker.io/mariadb:11.8 - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 - environment: - MYSQL_ROOT_PASSWORD: 123 - MARIADB_AUTO_UPGRADE: 1 - volumes: - - mariadb-data:/var/lib/mysql - - # Enable PostgreSQL only if you use it, see development/README.md for more information. - # postgresql: - # image: postgres:14 - # environment: - # POSTGRES_PASSWORD: 123 - # volumes: - # - postgresql-data:/var/lib/postgresql/data - - # Enable Mailpit if you need to test outgoing mail services - # See https://mailpit.axllent.org/ - # mailpit: - # image: axllent/mailpit - # volumes: - # - mailpit-data:/data - # ports: - # - 8025:8025 - # - 1025:1025 - # environment: - # MP_MAX_MESSAGES: 5000 - # MP_DATA_FILE: /data/mailpit.db - # MP_SMTP_AUTH_ACCEPT_ANY: 1 - # MP_SMTP_AUTH_ALLOW_INSECURE: 1 - - redis-cache: - image: docker.io/redis:alpine - - redis-queue: - image: docker.io/redis:alpine - - frappe: - image: docker.io/frappe/bench:latest - # If you want to build the current bench image the Containerfile is in this Repo. - # build: ../images/bench - command: sleep infinity - environment: - - SHELL=/bin/bash - volumes: - - ..:/workspace:cached - # Enable if you require git cloning - # - ${HOME}/.ssh:/home/frappe/.ssh - working_dir: /workspace/development - ports: - - 8000-8005:8000-8005 - - 9000-9005:9000-9005 - # enable the below service if you need Cypress UI Tests to be executed - # Before enabling ensure install_x11_deps.sh has been executed and display variable is exported. - # Run install_x11_deps.sh again if DISPLAY is not set - # ui-tester: - # # pass custom command to start Cypress otherwise it will use the entrypoint - # # specified in the Cypress Docker image. - # # also pass "--project " so that when Cypress opens - # # it can find file "cypress.json" and show integration specs - # # https://on.cypress.io/command-line#cypress-open - # entrypoint: 'sleep infinity' - # image: "docker.io/cypress/included:latest" - # environment: - # - SHELL=/bin/bash - # # get the IP address of the host machine and allow X11 to accept - # # incoming connections from that IP address - # # IP=$(ipconfig getifaddr en0) or mac or \ - # # IP=$($(hostname -I | awk '{print $1}') ) for Ubuntu - # # /usr/X11/bin/xhost + $IP - # # then pass the environment variable DISPLAY to show Cypress GUI on the host system - # # DISPLAY=$IP:0 - # - DISPLAY - # volumes: - # # for Cypress to communicate with the X11 server pass this socket file - # # in addition to any other mapped volumes - # - /tmp/.X11-unix:/tmp/.X11-unix - # - ..:/workspace:z,cached - # network_mode: "host" -volumes: - mariadb-data: - #postgresql-data: - #mailpit-data: +version: "3.7" +services: + mariadb: + image: docker.io/mariadb:11.8 + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 + environment: + MYSQL_ROOT_PASSWORD: 123 + MARIADB_AUTO_UPGRADE: 1 + volumes: + - mariadb-data:/var/lib/mysql + + # Enable PostgreSQL only if you use it, see development/README.md for more information. + # postgresql: + # image: postgres:14 + # environment: + # POSTGRES_PASSWORD: 123 + # volumes: + # - postgresql-data:/var/lib/postgresql/data + + # Enable Mailpit if you need to test outgoing mail services + # See https://mailpit.axllent.org/ + # mailpit: + # image: axllent/mailpit + # volumes: + # - mailpit-data:/data + # ports: + # - 8025:8025 + # - 1025:1025 + # environment: + # MP_MAX_MESSAGES: 5000 + # MP_DATA_FILE: /data/mailpit.db + # MP_SMTP_AUTH_ACCEPT_ANY: 1 + # MP_SMTP_AUTH_ALLOW_INSECURE: 1 + + redis-cache: + image: docker.io/redis:alpine + + redis-queue: + image: docker.io/redis:alpine + + frappe: + image: docker.io/frappe/bench:latest + # If you want to build the current bench image the Containerfile is in this Repo. + # build: ../images/bench + command: sleep infinity + environment: + - SHELL=/bin/bash + volumes: + - ..:/workspace:cached + # Enable if you require git cloning + # - ${HOME}/.ssh:/home/frappe/.ssh + working_dir: /workspace/development + ports: + - 8000-8005:8000-8005 + - 9000-9005:9000-9005 + # enable the below service if you need Cypress UI Tests to be executed + # Before enabling ensure install_x11_deps.sh has been executed and display variable is exported. + # Run install_x11_deps.sh again if DISPLAY is not set + # ui-tester: + # # pass custom command to start Cypress otherwise it will use the entrypoint + # # specified in the Cypress Docker image. + # # also pass "--project " so that when Cypress opens + # # it can find file "cypress.json" and show integration specs + # # https://on.cypress.io/command-line#cypress-open + # entrypoint: 'sleep infinity' + # image: "docker.io/cypress/included:latest" + # environment: + # - SHELL=/bin/bash + # # get the IP address of the host machine and allow X11 to accept + # # incoming connections from that IP address + # # IP=$(ipconfig getifaddr en0) or mac or \ + # # IP=$($(hostname -I | awk '{print $1}') ) for Ubuntu + # # /usr/X11/bin/xhost + $IP + # # then pass the environment variable DISPLAY to show Cypress GUI on the host system + # # DISPLAY=$IP:0 + # - DISPLAY + # volumes: + # # for Cypress to communicate with the X11 server pass this socket file + # # in addition to any other mapped volumes + # - /tmp/.X11-unix:/tmp/.X11-unix + # - ..:/workspace:z,cached + # network_mode: "host" +volumes: + mariadb-data: + #postgresql-data: + #mailpit-data: diff --git a/development/apps-example.json b/development/apps-example.json index 513d3d10..353280da 100644 --- a/development/apps-example.json +++ b/development/apps-example.json @@ -1,6 +1,6 @@ -[ - { - "url": "https://github.com/frappe/erpnext.git", - "branch": "version-15" - } -] +[ + { + "url": "https://github.com/frappe/erpnext.git", + "branch": "version-15" + } +] diff --git a/development/installer.py b/development/installer.py index edd62147..3a2d3c91 100755 --- a/development/installer.py +++ b/development/installer.py @@ -1,245 +1,247 @@ -#!/usr/bin/env python3 -import argparse -import os -import subprocess - - -def cprint(*args, level: int = 1): - """ - logs colorful messages - level = 1 : RED - level = 2 : GREEN - level = 3 : YELLOW - - default level = 1 - """ - CRED = "\033[31m" - CGRN = "\33[92m" - CYLW = "\33[93m" - reset = "\033[0m" - message = " ".join(map(str, args)) - if level == 1: - print(CRED, message, reset) # noqa: T001, T201 - if level == 2: - print(CGRN, message, reset) # noqa: T001, T201 - if level == 3: - print(CYLW, message, reset) # noqa: T001, T201 - - -def main(): - parser = get_args_parser() - args = parser.parse_args() - init_bench_if_not_exist(args) - create_site_in_bench(args) - - -def get_args_parser(): - parser = argparse.ArgumentParser() - parser.add_argument( - "-j", - "--apps-json", - action="store", - type=str, - help="Path to apps.json, default: apps-example.json", - default="apps-example.json", - ) # noqa: E501 - parser.add_argument( - "-b", - "--bench-name", - action="store", - type=str, - help="Bench directory name, default: frappe-bench", - default="frappe-bench", - ) # noqa: E501 - parser.add_argument( - "-s", - "--site-name", - action="store", - type=str, - help="Site name, should end with .localhost, default: development.localhost", # noqa: E501 - default="development.localhost", - ) - parser.add_argument( - "-r", - "--frappe-repo", - action="store", - type=str, - help="frappe repo to use, default: https://github.com/frappe/frappe", # noqa: E501 - default="https://github.com/frappe/frappe", - ) - parser.add_argument( - "-t", - "--frappe-branch", - action="store", - type=str, - help="frappe repo to use, default: version-15", # noqa: E501 - default="version-15", - ) - parser.add_argument( - "-p", - "--py-version", - action="store", - type=str, - help="python version, default: Not Set", # noqa: E501 - default=None, - ) - parser.add_argument( - "-n", - "--node-version", - action="store", - type=str, - help="node version, default: Not Set", # noqa: E501 - default=None, - ) - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="verbose output", # noqa: E501 - ) - parser.add_argument( - "-a", - "--admin-password", - action="store", - type=str, - help="admin password for site, default: admin", # noqa: E501 - default="admin", - ) - parser.add_argument( - "-d", - "--db-type", - action="store", - type=str, - help="Database type to use (e.g., mariadb or postgres)", - default="mariadb", # Set your default database type here - ) - return parser - - -def init_bench_if_not_exist(args): - if os.path.exists(args.bench_name): - cprint("Bench already exists. Only site will be created", level=3) - return - try: - env = os.environ.copy() - if args.py_version: - env["PYENV_VERSION"] = args.py_version - init_command = "" - if args.node_version: - init_command = f"nvm use {args.node_version};" - if args.py_version: - init_command += f"PYENV_VERSION={args.py_version} " - init_command += "bench init " - init_command += "--skip-redis-config-generation " - init_command += "--verbose " if args.verbose else " " - init_command += f"--frappe-path={args.frappe_repo} " - init_command += f"--frappe-branch={args.frappe_branch} " - init_command += f"--apps_path={args.apps_json} " - init_command += args.bench_name - command = [ - "/bin/bash", - "-i", - "-c", - init_command, - ] - subprocess.call(command, env=env, cwd=os.getcwd()) - cprint("Configuring Bench ...", level=2) - cprint("Set db_host", level=3) - if args.db_type: - cprint(f"Setting db_type to {args.db_type}", level=3) - subprocess.call( - ["bench", "set-config", "-g", "db_type", args.db_type], - cwd=os.path.join(os.getcwd(), args.bench_name), - ) - - cprint("Set redis_cache to redis://redis-cache:6379", level=3) - subprocess.call( - [ - "bench", - "set-config", - "-g", - "redis_cache", - "redis://redis-cache:6379", - ], - cwd=os.getcwd() + "/" + args.bench_name, - ) - cprint("Set redis_queue to redis://redis-queue:6379", level=3) - subprocess.call( - [ - "bench", - "set-config", - "-g", - "redis_queue", - "redis://redis-queue:6379", - ], - cwd=os.getcwd() + "/" + args.bench_name, - ) - cprint( - "Set redis_socketio to redis://redis-queue:6379 for backward compatibility", # noqa: E501 - level=3, - ) - subprocess.call( - [ - "bench", - "set-config", - "-g", - "redis_socketio", - "redis://redis-queue:6379", - ], - cwd=os.getcwd() + "/" + args.bench_name, - ) - cprint("Set developer_mode", level=3) - subprocess.call( - ["bench", "set-config", "-gp", "developer_mode", "1"], - cwd=os.getcwd() + "/" + args.bench_name, - ) - except subprocess.CalledProcessError as e: - cprint(e.output, level=1) - - -def create_site_in_bench(args): - if "mariadb" == args.db_type: - cprint("Set db_host", level=3) - subprocess.call( - ["bench", "set-config", "-g", "db_host", "mariadb"], - cwd=os.getcwd() + "/" + args.bench_name, - ) - new_site_cmd = [ - "bench", - "new-site", - f"--db-root-username=root", - f"--db-host=mariadb", # Should match the compose service name - f"--db-type={args.db_type}", # Add the selected database type - f"--mariadb-user-host-login-scope=%", - f"--db-root-password=123", # Replace with your MariaDB password - f"--admin-password={args.admin_password}", - ] - else: - cprint("Set db_host", level=3) - subprocess.call( - ["bench", "set-config", "-g", "db_host", "postgresql"], - cwd=os.getcwd() + "/" + args.bench_name, - ) - new_site_cmd = [ - "bench", - "new-site", - f"--db-root-username=root", - f"--db-host=postgresql", # Should match the compose service name - f"--db-type={args.db_type}", # Add the selected database type - f"--db-root-password=123", # Replace with your PostgreSQL password - f"--admin-password={args.admin_password}", - ] - apps = os.listdir(f"{os.getcwd()}/{args.bench_name}/apps") - apps.remove("frappe") - for app in apps: - new_site_cmd.append(f"--install-app={app}") - new_site_cmd.append(args.site_name) - cprint(f"Creating Site {args.site_name} ...", level=2) - subprocess.call( - new_site_cmd, - cwd=os.getcwd() + "/" + args.bench_name, - ) - - -if __name__ == "__main__": - main() +#!/usr/bin/env python3 +import argparse +import os +import subprocess + + +def cprint(*args, level: int = 1): + """ + logs colorful messages + level = 1 : RED + level = 2 : GREEN + level = 3 : YELLOW + + default level = 1 + """ + CRED = "\033[31m" + CGRN = "\33[92m" + CYLW = "\33[93m" + reset = "\033[0m" + message = " ".join(map(str, args)) + if level == 1: + print(CRED, message, reset) # noqa: T001, T201 + if level == 2: + print(CGRN, message, reset) # noqa: T001, T201 + if level == 3: + print(CYLW, message, reset) # noqa: T001, T201 + + +def main(): + parser = get_args_parser() + args = parser.parse_args() + init_bench_if_not_exist(args) + create_site_in_bench(args) + + +def get_args_parser(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-j", + "--apps-json", + action="store", + type=str, + help="Path to apps.json, default: apps-example.json", + default="apps-example.json", + ) # noqa: E501 + parser.add_argument( + "-b", + "--bench-name", + action="store", + type=str, + help="Bench directory name, default: frappe-bench", + default="frappe-bench", + ) # noqa: E501 + parser.add_argument( + "-s", + "--site-name", + action="store", + type=str, + help="Site name, should end with .localhost, default: development.localhost", # noqa: E501 + default="development.localhost", + ) + parser.add_argument( + "-r", + "--frappe-repo", + action="store", + type=str, + help="frappe repo to use, default: https://github.com/frappe/frappe", # noqa: E501 + default="https://github.com/frappe/frappe", + ) + parser.add_argument( + "-t", + "--frappe-branch", + action="store", + type=str, + help="frappe repo to use, default: version-15", # noqa: E501 + default="version-15", + ) + parser.add_argument( + "-p", + "--py-version", + action="store", + type=str, + help="python version, default: Not Set", # noqa: E501 + default=None, + ) + parser.add_argument( + "-n", + "--node-version", + action="store", + type=str, + help="node version, default: Not Set", # noqa: E501 + default=None, + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="verbose output", # noqa: E501 + ) + parser.add_argument( + "-a", + "--admin-password", + action="store", + type=str, + help="admin password for site, default: admin", # noqa: E501 + default="admin", + ) + parser.add_argument( + "-d", + "--db-type", + action="store", + type=str, + help="Database type to use (e.g., mariadb or postgres)", + default="mariadb", # Set your default database type here + ) + return parser + + +def init_bench_if_not_exist(args): + if os.path.exists(args.bench_name): + cprint("Bench already exists. Only site will be created", level=3) + return + try: + env = os.environ.copy() + if args.py_version: + env["PYENV_VERSION"] = args.py_version + init_command = "" + if args.node_version: + init_command = f"nvm use {args.node_version};" + if args.py_version: + init_command += f"PYENV_VERSION={args.py_version} " + init_command += "bench init " + init_command += "--skip-redis-config-generation " + init_command += "--verbose " if args.verbose else " " + init_command += f"--frappe-path={args.frappe_repo} " + init_command += f"--frappe-branch={args.frappe_branch} " + init_command += f"--apps_path={args.apps_json} " + init_command += args.bench_name + command = [ + "/bin/bash", + "-i", + "-c", + init_command, + ] + subprocess.call(command, env=env, cwd=os.getcwd()) + cprint("Configuring Bench ...", level=2) + cprint("Set db_host", level=3) + if args.db_type: + cprint(f"Setting db_type to {args.db_type}", level=3) + subprocess.call( + ["bench", "set-config", "-g", "db_type", args.db_type], + cwd=os.path.join(os.getcwd(), args.bench_name), + ) + + cprint("Set redis_cache to redis://redis-cache:6379", level=3) + subprocess.call( + [ + "bench", + "set-config", + "-g", + "redis_cache", + "redis://redis-cache:6379", + ], + cwd=os.getcwd() + "/" + args.bench_name, + ) + cprint("Set redis_queue to redis://redis-queue:6379", level=3) + subprocess.call( + [ + "bench", + "set-config", + "-g", + "redis_queue", + "redis://redis-queue:6379", + ], + cwd=os.getcwd() + "/" + args.bench_name, + ) + cprint( + "Set redis_socketio to redis://redis-queue:6379 for backward compatibility", # noqa: E501 + level=3, + ) + subprocess.call( + [ + "bench", + "set-config", + "-g", + "redis_socketio", + "redis://redis-queue:6379", + ], + cwd=os.getcwd() + "/" + args.bench_name, + ) + cprint("Set developer_mode", level=3) + subprocess.call( + ["bench", "set-config", "-gp", "developer_mode", "1"], + cwd=os.getcwd() + "/" + args.bench_name, + ) + except subprocess.CalledProcessError as e: + cprint(e.output, level=1) + + +def create_site_in_bench(args): + if "mariadb" == args.db_type: + cprint("Set db_host", level=3) + subprocess.call( + ["bench", "set-config", "-g", "db_host", "mariadb"], + cwd=os.getcwd() + "/" + args.bench_name, + ) + new_site_cmd = [ + "bench", + "new-site", + f"--db-root-username=root", + f"--db-host=mariadb", # Should match the compose service name + f"--db-type={args.db_type}", # Add the selected database type + f"--mariadb-user-host-login-scope=%", + f"--db-root-password=123", # Replace with your MariaDB password + f"--admin-password={args.admin_password}", + ] + else: + cprint("Set db_host", level=3) + subprocess.call( + ["bench", "set-config", "-g", "db_host", "postgresql"], + cwd=os.getcwd() + "/" + args.bench_name, + ) + new_site_cmd = [ + "bench", + "new-site", + f"--db-root-username=root", + f"--db-host=postgresql", # Should match the compose service name + f"--db-type={args.db_type}", # Add the selected database type + f"--db-root-password=123", # Replace with your PostgreSQL password + f"--admin-password={args.admin_password}", + ] + apps = os.listdir(f"{os.getcwd()}/{args.bench_name}/apps") + apps.remove("frappe") + for app in apps: + print(app) + new_site_cmd.append(f"--install-app={app}") + print(new_site_cmd) + new_site_cmd.append(args.site_name) + cprint(f"Creating Site {args.site_name} ...", level=2) + subprocess.call( + new_site_cmd, + cwd=os.getcwd() + "/" + args.bench_name, + ) + + +if __name__ == "__main__": + main() diff --git a/development/vscode-example/launch.json b/development/vscode-example/launch.json index 7267fcf3..fe503245 100644 --- a/development/vscode-example/launch.json +++ b/development/vscode-example/launch.json @@ -1,77 +1,77 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Bench Web", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", - "args": [ - "frappe", - "serve", - "--port", - "8000", - "--noreload", - "--nothreading" - ], - "cwd": "${workspaceFolder}/frappe-bench/sites", - "env": { - "DEV_SERVER": "1" - } - }, - { - "name": "Bench Short Worker", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", - "args": ["frappe", "worker", "--queue", "short"], - "cwd": "${workspaceFolder}/frappe-bench/sites", - "env": { - "DEV_SERVER": "1" - } - }, - { - "name": "Bench Default Worker", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", - "args": ["frappe", "worker", "--queue", "default"], - "cwd": "${workspaceFolder}/frappe-bench/sites", - "env": { - "DEV_SERVER": "1" - } - }, - { - "name": "Bench Long Worker", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", - "args": ["frappe", "worker", "--queue", "long"], - "cwd": "${workspaceFolder}/frappe-bench/sites", - "env": { - "DEV_SERVER": "1" - } - }, - { - "name": "Honcho SocketIO Watch Schedule Worker", - "type": "debugpy", - "request": "launch", - "python": "/home/frappe/.pyenv/shims/python", - "program": "/home/frappe/.local/bin/honcho", - "cwd": "${workspaceFolder}/frappe-bench", - "console": "internalConsole", - "args": ["start", "socketio", "watch", "schedule", "worker"], - "postDebugTask": "Clean Honcho SocketIO Watch Schedule Worker" - } - ], - "compounds": [ - { - "name": "Honcho + Web debug", - "configurations": ["Bench Web", "Honcho SocketIO Watch Schedule Worker"], - "stopAll": true - } - ] -} +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Bench Web", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": [ + "frappe", + "serve", + "--port", + "8000", + "--noreload", + "--nothreading" + ], + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } + }, + { + "name": "Bench Short Worker", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": ["frappe", "worker", "--queue", "short"], + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } + }, + { + "name": "Bench Default Worker", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": ["frappe", "worker", "--queue", "default"], + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } + }, + { + "name": "Bench Long Worker", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": ["frappe", "worker", "--queue", "long"], + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } + }, + { + "name": "Honcho SocketIO Watch Schedule Worker", + "type": "debugpy", + "request": "launch", + "python": "/home/frappe/.pyenv/shims/python", + "program": "/home/frappe/.local/bin/honcho", + "cwd": "${workspaceFolder}/frappe-bench", + "console": "internalConsole", + "args": ["start", "socketio", "watch", "schedule", "worker"], + "postDebugTask": "Clean Honcho SocketIO Watch Schedule Worker" + } + ], + "compounds": [ + { + "name": "Honcho + Web debug", + "configurations": ["Bench Web", "Honcho SocketIO Watch Schedule Worker"], + "stopAll": true + } + ] +} diff --git a/development/vscode-example/settings.json b/development/vscode-example/settings.json index 1490b727..e7e43feb 100644 --- a/development/vscode-example/settings.json +++ b/development/vscode-example/settings.json @@ -1,3 +1,3 @@ -{ - "python.defaultInterpreterPath": "${workspaceFolder}/frappe-bench/env/bin/python" -} +{ + "python.defaultInterpreterPath": "${workspaceFolder}/frappe-bench/env/bin/python" +} diff --git a/development/vscode-example/tasks.json b/development/vscode-example/tasks.json index 7c0e6739..6ee0aab3 100644 --- a/development/vscode-example/tasks.json +++ b/development/vscode-example/tasks.json @@ -1,22 +1,22 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "Clean Honcho SocketIO Watch Schedule Worker", - "detail": "When stopping the debug process from vscode window, the honcho won't receive the SIGINT signal. This task will send the SIGINT signal to the honcho processes.", - "type": "shell", - "command": "pkill -SIGINT -f bench; pkill -SIGINT -f socketio", - "isBackground": false, - "presentation": { - "echo": true, - "reveal": "silent", - "focus": false, - "panel": "shared", - "showReuseMessage": false, - "close": true - } - } - ] -} +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Clean Honcho SocketIO Watch Schedule Worker", + "detail": "When stopping the debug process from vscode window, the honcho won't receive the SIGINT signal. This task will send the SIGINT signal to the honcho processes.", + "type": "shell", + "command": "pkill -SIGINT -f bench; pkill -SIGINT -f socketio", + "isBackground": false, + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "close": true + } + } + ] +} diff --git a/docker-bake.hcl b/docker-bake.hcl index 9d3ccd00..a0044098 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,113 +1,113 @@ -# Docker Buildx Bake build definition file -# Reference: https://github.com/docker/buildx/blob/master/docs/reference/buildx_bake.md - -variable "REGISTRY_USER" { - default = "frappe" -} - -variable PYTHON_VERSION { - default = "3.11.6" -} -variable NODE_VERSION { - default = "20.19.2" -} - -variable "FRAPPE_VERSION" { - default = "develop" -} - -variable "ERPNEXT_VERSION" { - default = "develop" -} - -variable "FRAPPE_REPO" { - default = "https://github.com/frappe/frappe" -} - -variable "ERPNEXT_REPO" { - default = "https://github.com/frappe/erpnext" -} - -variable "BENCH_REPO" { - default = "https://github.com/frappe/bench" -} - -variable "LATEST_BENCH_RELEASE" { - default = "latest" -} - -# Bench image - -target "bench" { - args = { - GIT_REPO = "${BENCH_REPO}" - } - context = "images/bench" - target = "bench" - tags = [ - "frappe/bench:${LATEST_BENCH_RELEASE}", - "frappe/bench:latest", - ] -} - -target "bench-test" { - inherits = ["bench"] - target = "bench-test" -} - -# Main images -# Base for all other targets - -group "default" { - targets = ["erpnext", "base", "build"] -} - -function "tag" { - params = [repo, version] - result = [ - # Push frappe or erpnext branch as tag - "${REGISTRY_USER}/${repo}:${version}", - # If `version` param is develop (development build) then use tag `latest` - "${version}" == "develop" ? "${REGISTRY_USER}/${repo}:latest" : "${REGISTRY_USER}/${repo}:${version}", - # Make short tag for major version if possible. For example, from v13.16.0 make v13. - can(regex("(v[0-9]+)[.]", "${version}")) ? "${REGISTRY_USER}/${repo}:${regex("(v[0-9]+)[.]", "${version}")[0]}" : "", - # Make short tag for major version if possible. For example, from v13.16.0 make version-13. - can(regex("(v[0-9]+)[.]", "${version}")) ? "${REGISTRY_USER}/${repo}:version-${regex("([0-9]+)[.]", "${version}")[0]}" : "", - ] -} - -target "default-args" { - args = { - FRAPPE_PATH = "${FRAPPE_REPO}" - ERPNEXT_PATH = "${ERPNEXT_REPO}" - BENCH_REPO = "${BENCH_REPO}" - FRAPPE_BRANCH = "${FRAPPE_VERSION}" - ERPNEXT_BRANCH = "${ERPNEXT_VERSION}" - PYTHON_VERSION = "${PYTHON_VERSION}" - NODE_VERSION = "${NODE_VERSION}" - } -} - -target "erpnext" { - inherits = ["default-args"] - context = "." - dockerfile = "images/production/Containerfile" - target = "erpnext" - tags = tag("erpnext", "${ERPNEXT_VERSION}") -} - -target "base" { - inherits = ["default-args"] - context = "." - dockerfile = "images/production/Containerfile" - target = "base" - tags = tag("base", "${FRAPPE_VERSION}") -} - -target "build" { - inherits = ["default-args"] - context = "." - dockerfile = "images/production/Containerfile" - target = "build" - tags = tag("build", "${ERPNEXT_VERSION}") -} +# Docker Buildx Bake build definition file +# Reference: https://github.com/docker/buildx/blob/master/docs/reference/buildx_bake.md + +variable "REGISTRY_USER" { + default = "frappe" +} + +variable PYTHON_VERSION { + default = "3.11.6" +} +variable NODE_VERSION { + default = "20.19.2" +} + +variable "FRAPPE_VERSION" { + default = "develop" +} + +variable "ERPNEXT_VERSION" { + default = "develop" +} + +variable "FRAPPE_REPO" { + default = "https://github.com/frappe/frappe" +} + +variable "ERPNEXT_REPO" { + default = "https://github.com/frappe/erpnext" +} + +variable "BENCH_REPO" { + default = "https://github.com/frappe/bench" +} + +variable "LATEST_BENCH_RELEASE" { + default = "latest" +} + +# Bench image + +target "bench" { + args = { + GIT_REPO = "${BENCH_REPO}" + } + context = "images/bench" + target = "bench" + tags = [ + "frappe/bench:${LATEST_BENCH_RELEASE}", + "frappe/bench:latest", + ] +} + +target "bench-test" { + inherits = ["bench"] + target = "bench-test" +} + +# Main images +# Base for all other targets + +group "default" { + targets = ["erpnext", "base", "build"] +} + +function "tag" { + params = [repo, version] + result = [ + # Push frappe or erpnext branch as tag + "${REGISTRY_USER}/${repo}:${version}", + # If `version` param is develop (development build) then use tag `latest` + "${version}" == "develop" ? "${REGISTRY_USER}/${repo}:latest" : "${REGISTRY_USER}/${repo}:${version}", + # Make short tag for major version if possible. For example, from v13.16.0 make v13. + can(regex("(v[0-9]+)[.]", "${version}")) ? "${REGISTRY_USER}/${repo}:${regex("(v[0-9]+)[.]", "${version}")[0]}" : "", + # Make short tag for major version if possible. For example, from v13.16.0 make version-13. + can(regex("(v[0-9]+)[.]", "${version}")) ? "${REGISTRY_USER}/${repo}:version-${regex("([0-9]+)[.]", "${version}")[0]}" : "", + ] +} + +target "default-args" { + args = { + FRAPPE_PATH = "${FRAPPE_REPO}" + ERPNEXT_PATH = "${ERPNEXT_REPO}" + BENCH_REPO = "${BENCH_REPO}" + FRAPPE_BRANCH = "${FRAPPE_VERSION}" + ERPNEXT_BRANCH = "${ERPNEXT_VERSION}" + PYTHON_VERSION = "${PYTHON_VERSION}" + NODE_VERSION = "${NODE_VERSION}" + } +} + +target "erpnext" { + inherits = ["default-args"] + context = "." + dockerfile = "images/production/Containerfile" + target = "erpnext" + tags = tag("erpnext", "${ERPNEXT_VERSION}") +} + +target "base" { + inherits = ["default-args"] + context = "." + dockerfile = "images/production/Containerfile" + target = "base" + tags = tag("base", "${FRAPPE_VERSION}") +} + +target "build" { + inherits = ["default-args"] + context = "." + dockerfile = "images/production/Containerfile" + target = "build" + tags = tag("build", "${ERPNEXT_VERSION}") +} diff --git a/docs/backup-and-push-cronjob.md b/docs/backup-and-push-cronjob.md index ce5d8b49..de5477a7 100644 --- a/docs/backup-and-push-cronjob.md +++ b/docs/backup-and-push-cronjob.md @@ -1,58 +1,58 @@ -Create backup service or stack. - -```yaml -# backup-job.yml -version: "3.7" -services: - backup: - image: frappe/erpnext:${VERSION} - entrypoint: ["bash", "-c"] - command: - - | - bench --site all backup - ## Uncomment for restic snapshots. - # restic snapshots || restic init - # restic backup sites - ## Uncomment to keep only last n=30 snapshots. - # restic forget --group-by=paths --keep-last=30 --prune - environment: - # Set correct environment variables for restic - - RESTIC_REPOSITORY=s3:https://s3.endpoint.com/restic - - AWS_ACCESS_KEY_ID=access_key - - AWS_SECRET_ACCESS_KEY=secret_access_key - - RESTIC_PASSWORD=restic_password - volumes: - - "sites:/home/frappe/frappe-bench/sites" - networks: - - erpnext-network - -networks: - erpnext-network: - external: true - name: ${PROJECT_NAME:-erpnext}_default - -volumes: - sites: - external: true - name: ${PROJECT_NAME:-erpnext}_sites -``` - -In case of single docker host setup, add crontab entry for backup every 6 hours. - -``` -0 */6 * * * /usr/local/bin/docker-compose -f /path/to/backup-job.yml up -d > /dev/null -``` - -Or - -``` -0 */6 * * * docker compose -p erpnext exec backend bench --site all backup --with-files > /dev/null -``` - -Notes: - -- Make sure `docker-compose` or `docker compose` is available in path during execution. -- Change the cron string as per need. -- Set the correct project name in place of `erpnext`. -- For Docker Swarm add it as a [swarm-cronjob](https://github.com/crazy-max/swarm-cronjob) -- Add it as a `CronJob` in case of Kubernetes cluster. +Create backup service or stack. + +```yaml +# backup-job.yml +version: "3.7" +services: + backup: + image: frappe/erpnext:${VERSION} + entrypoint: ["bash", "-c"] + command: + - | + bench --site all backup + ## Uncomment for restic snapshots. + # restic snapshots || restic init + # restic backup sites + ## Uncomment to keep only last n=30 snapshots. + # restic forget --group-by=paths --keep-last=30 --prune + environment: + # Set correct environment variables for restic + - RESTIC_REPOSITORY=s3:https://s3.endpoint.com/restic + - AWS_ACCESS_KEY_ID=access_key + - AWS_SECRET_ACCESS_KEY=secret_access_key + - RESTIC_PASSWORD=restic_password + volumes: + - "sites:/home/frappe/frappe-bench/sites" + networks: + - erpnext-network + +networks: + erpnext-network: + external: true + name: ${PROJECT_NAME:-erpnext}_default + +volumes: + sites: + external: true + name: ${PROJECT_NAME:-erpnext}_sites +``` + +In case of single docker host setup, add crontab entry for backup every 6 hours. + +``` +0 */6 * * * /usr/local/bin/docker-compose -f /path/to/backup-job.yml up -d > /dev/null +``` + +Or + +``` +0 */6 * * * docker compose -p erpnext exec backend bench --site all backup --with-files > /dev/null +``` + +Notes: + +- Make sure `docker-compose` or `docker compose` is available in path during execution. +- Change the cron string as per need. +- Set the correct project name in place of `erpnext`. +- For Docker Swarm add it as a [swarm-cronjob](https://github.com/crazy-max/swarm-cronjob) +- Add it as a `CronJob` in case of Kubernetes cluster. diff --git a/docs/bench-console-and-vscode-debugger.md b/docs/bench-console-and-vscode-debugger.md index d506be0f..44c956f3 100644 --- a/docs/bench-console-and-vscode-debugger.md +++ b/docs/bench-console-and-vscode-debugger.md @@ -1,16 +1,16 @@ -Add the following configuration to `launch.json` `configurations` array to start bench console and use debugger. Replace `development.localhost` with appropriate site. Also replace `frappe-bench` with name of the bench directory. - -```json -{ - "name": "Bench Console", - "type": "python", - "request": "launch", - "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", - "args": ["frappe", "--site", "development.localhost", "console"], - "pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python", - "cwd": "${workspaceFolder}/frappe-bench/sites", - "env": { - "DEV_SERVER": "1" - } -} -``` +Add the following configuration to `launch.json` `configurations` array to start bench console and use debugger. Replace `development.localhost` with appropriate site. Also replace `frappe-bench` with name of the bench directory. + +```json +{ + "name": "Bench Console", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/frappe-bench/apps/frappe/frappe/utils/bench_helper.py", + "args": ["frappe", "--site", "development.localhost", "console"], + "pythonPath": "${workspaceFolder}/frappe-bench/env/bin/python", + "cwd": "${workspaceFolder}/frappe-bench/sites", + "env": { + "DEV_SERVER": "1" + } +} +``` diff --git a/docs/build-version-10-images.md b/docs/build-version-10-images.md index 613f06ee..e722b629 100644 --- a/docs/build-version-10-images.md +++ b/docs/build-version-10-images.md @@ -1,16 +1,16 @@ -Clone the version-10 branch of this repo - -```shell -git clone https://github.com/frappe/frappe_docker.git -b version-10 && cd frappe_docker -``` - -Build the images - -```shell -export DOCKER_REGISTRY_PREFIX=frappe -docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-socketio:v10 -f build/frappe-socketio/Dockerfile . -docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-nginx:v10 -f build/frappe-nginx/Dockerfile . -docker build -t ${DOCKER_REGISTRY_PREFIX}/erpnext-nginx:v10 -f build/erpnext-nginx/Dockerfile . -docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-worker:v10 -f build/frappe-worker/Dockerfile . -docker build -t ${DOCKER_REGISTRY_PREFIX}/erpnext-worker:v10 -f build/erpnext-worker/Dockerfile . -``` +Clone the version-10 branch of this repo + +```shell +git clone https://github.com/frappe/frappe_docker.git -b version-10 && cd frappe_docker +``` + +Build the images + +```shell +export DOCKER_REGISTRY_PREFIX=frappe +docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-socketio:v10 -f build/frappe-socketio/Dockerfile . +docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-nginx:v10 -f build/frappe-nginx/Dockerfile . +docker build -t ${DOCKER_REGISTRY_PREFIX}/erpnext-nginx:v10 -f build/erpnext-nginx/Dockerfile . +docker build -t ${DOCKER_REGISTRY_PREFIX}/frappe-worker:v10 -f build/frappe-worker/Dockerfile . +docker build -t ${DOCKER_REGISTRY_PREFIX}/erpnext-worker:v10 -f build/erpnext-worker/Dockerfile . +``` diff --git a/docs/connect-to-localhost-services-from-containers-for-local-app-development.md b/docs/connect-to-localhost-services-from-containers-for-local-app-development.md index c76b52a2..91170912 100644 --- a/docs/connect-to-localhost-services-from-containers-for-local-app-development.md +++ b/docs/connect-to-localhost-services-from-containers-for-local-app-development.md @@ -1,13 +1,13 @@ -Add following to frappe container from the `.devcontainer/docker-compose.yml`: - -```yaml -... - frappe: - ... - extra_hosts: - app1.localhost: 172.17.0.1 - app2.localhost: 172.17.0.1 -... -``` - -This is makes the domain names `app1.localhost` and `app2.localhost` connect to docker host and connect to services running on `localhost`. +Add following to frappe container from the `.devcontainer/docker-compose.yml`: + +```yaml +... + frappe: + ... + extra_hosts: + app1.localhost: 172.17.0.1 + app2.localhost: 172.17.0.1 +... +``` + +This is makes the domain names `app1.localhost` and `app2.localhost` connect to docker host and connect to services running on `localhost`. diff --git a/docs/container-setup/01-overview.md b/docs/container-setup/01-overview.md index ee266886..cf529ede 100644 --- a/docs/container-setup/01-overview.md +++ b/docs/container-setup/01-overview.md @@ -1,47 +1,47 @@ -The purpose of this document is to give you an overview of how the Frappe Docker containers are structured. - -# 🐳 Images - -There are **four predefined Dockerfiles** available in the `/images` directory. - -| Dockerfile | Ingredients | Purpose & Use Case | -| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **bench** | Sets up only the Bench CLI. | Used for **development** or debugging. Provides the command-line tooling but does not include runtime services. | -| **custom** | Multi-purpose Python backend built from a plain Python image. Includes everything needed to run a Frappe instance via a Compose setup. Installs apps defined in `apps.json`. | Suitable for **production** and **testing**. Ideal when you need control over dependencies (e.g. trying new Python or Node versions). | -| **layered** | Final contents are the same as `custom`, but it is based on **prebuilt images from [Docker Hub](https://hub.docker.com/u/frappe)**. | Great for **production builds** when you’re fine with the dependency versions managed by Frappe. Builds much faster since the base layers are already prepared. | -| **production** | Similar to `custom` (built from a Python base image), but installs **only Frappe and ERPNext**. Not customizable with `apps.json`. | Best for **quick starts** or exploration. For real deployments or CI/CD pipelines, `custom` or `layered` are preferred because they offer more flexibility. | - ---- - -These images include everything needed to run all processes required by the Frappe framework -(see [Bench Procfile reference](https://frappeframework.com/docs/v14/user/en/bench/resources/bench-procfile)). - -- The `bench` image only sets up the CLI tool. -- The other images (`custom`, `layered`, and `production`) go further — enabling a nearly **plug-and-play** setup for ERPNext and custom apps. - -> We use [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) and [Docker Buildx](https://docs.docker.com/engine/reference/commandline/buildx/) to maximize layer reuse and make our builds more efficient. - -# 🏗️ Compose - -Once images are built, containers are orchestrated using a [compose file](https://docs.docker.com/compose/compose-file/). The main compose.yaml provides core services, networking, and volumes for any Frappe setup. - -## 🛠️ Services - -| Service | Role | Purpose | -| ---------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **configurator** | Setup | Updates `common_site_config.json` so Frappe knows how to access db and redis. It is executed on every `docker-compose up` (and exited immediately). Other services start after this container exits successfully | -| **backend** | Runtime | [Werkzeug server](https://werkzeug.palletsprojects.com/en/2.0.x/) | -| **frontend** | Proxy | [nginx](https://www.nginx.com) server that serves JS/CSS assets and routes incoming requests | -| **websocket** | Real-time | Node server that runs [Socket.IO](https://socket.io) | -| **queue-\_** | Background Jobs | Python servers that run job queues using [rq](https://python-rq.org) | -| **scheduler** | Task Automation | Python server that runs tasks on schedule using [schedule](https://schedule.readthedocs.io/en/stable/) | - -## 🧩 Overrides - -Additional functionality can be added using [overrides](https://docs.docker.com/compose/extends/). These files modify existing services or add new ones without changing the main `compose.yaml`. - -Example: The main compose file has no database service, but `compose.mariadb.yaml` adds MariaDB. See [overrider.md](overrider.md) for the complete list of available overrides and how to use them. - ---- - -**Next:** [Build Setup →](02-build-setup.md) +The purpose of this document is to give you an overview of how the Frappe Docker containers are structured. + +# 🐳 Images + +There are **four predefined Dockerfiles** available in the `/images` directory. + +| Dockerfile | Ingredients | Purpose & Use Case | +| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **bench** | Sets up only the Bench CLI. | Used for **development** or debugging. Provides the command-line tooling but does not include runtime services. | +| **custom** | Multi-purpose Python backend built from a plain Python image. Includes everything needed to run a Frappe instance via a Compose setup. Installs apps defined in `apps.json`. | Suitable for **production** and **testing**. Ideal when you need control over dependencies (e.g. trying new Python or Node versions). | +| **layered** | Final contents are the same as `custom`, but it is based on **prebuilt images from [Docker Hub](https://hub.docker.com/u/frappe)**. | Great for **production builds** when you’re fine with the dependency versions managed by Frappe. Builds much faster since the base layers are already prepared. | +| **production** | Similar to `custom` (built from a Python base image), but installs **only Frappe and ERPNext**. Not customizable with `apps.json`. | Best for **quick starts** or exploration. For real deployments or CI/CD pipelines, `custom` or `layered` are preferred because they offer more flexibility. | + +--- + +These images include everything needed to run all processes required by the Frappe framework +(see [Bench Procfile reference](https://frappeframework.com/docs/v14/user/en/bench/resources/bench-procfile)). + +- The `bench` image only sets up the CLI tool. +- The other images (`custom`, `layered`, and `production`) go further — enabling a nearly **plug-and-play** setup for ERPNext and custom apps. + +> We use [multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/) and [Docker Buildx](https://docs.docker.com/engine/reference/commandline/buildx/) to maximize layer reuse and make our builds more efficient. + +# 🏗️ Compose + +Once images are built, containers are orchestrated using a [compose file](https://docs.docker.com/compose/compose-file/). The main compose.yaml provides core services, networking, and volumes for any Frappe setup. + +## 🛠️ Services + +| Service | Role | Purpose | +| ---------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **configurator** | Setup | Updates `common_site_config.json` so Frappe knows how to access db and redis. It is executed on every `docker-compose up` (and exited immediately). Other services start after this container exits successfully | +| **backend** | Runtime | [Werkzeug server](https://werkzeug.palletsprojects.com/en/2.0.x/) | +| **frontend** | Proxy | [nginx](https://www.nginx.com) server that serves JS/CSS assets and routes incoming requests | +| **websocket** | Real-time | Node server that runs [Socket.IO](https://socket.io) | +| **queue-\_** | Background Jobs | Python servers that run job queues using [rq](https://python-rq.org) | +| **scheduler** | Task Automation | Python server that runs tasks on schedule using [schedule](https://schedule.readthedocs.io/en/stable/) | + +## 🧩 Overrides + +Additional functionality can be added using [overrides](https://docs.docker.com/compose/extends/). These files modify existing services or add new ones without changing the main `compose.yaml`. + +Example: The main compose file has no database service, but `compose.mariadb.yaml` adds MariaDB. See [overrider.md](overrider.md) for the complete list of available overrides and how to use them. + +--- + +**Next:** [Build Setup →](02-build-setup.md) diff --git a/docs/container-setup/02-build-setup.md b/docs/container-setup/02-build-setup.md index 0e3f0e36..e079925b 100644 --- a/docs/container-setup/02-build-setup.md +++ b/docs/container-setup/02-build-setup.md @@ -1,121 +1,121 @@ -This guide walks you through building Frappe images from the repository resources. - -# Prerequisites - -- git -- docker or podman -- docker compose v2 or podman compose - -> Install containerization software according to the official maintainer documentation. Avoid package managers when not recommended, as they frequently cause compatibility issues. - -# Clone this repo - -```bash -git clone https://github.com/frappe/frappe_docker -cd frappe_docker -``` - -# Define custom apps - -If you dont want to install specific apps to the image skip this section. - -To include custom apps in your image, create an `apps.json` file in the repository root: - -```json -[ - { - "url": "https://github.com/frappe/erpnext", - "branch": "version-15" - }, - { - "url": "https://github.com/frappe/hrms", - "branch": "version-15" - }, - { - "url": "https://github.com/frappe/helpdesk", - "branch": "main" - } -] -``` - -Then generate a base64-encoded string from this file: - -```bash -export APPS_JSON_BASE64=$(base64 -w 0 apps.json) -``` - -# Build the image - -Choose the appropriate build command based on your container runtime and desired image type. This example builds the `layered` image with the custom `apps.json` you created. - -`Docker`: - -```bash -docker build \ - --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ - --build-arg=FRAPPE_BRANCH=version-15 \ - --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \ - --tag=custom:15 \ - --file=images/layered/Containerfile . -``` - -`Podman`: - -```bash -podman build \ - --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ - --build-arg=FRAPPE_BRANCH=version-15 \ - --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \ - --tag=custom:15 \ - --file=images/layered/Containerfile . -``` - -## Build args - -| Arg | Purpose | -| -------------------- | --------------------------------------------------------------------------------------------- | -| **Frappe Framework** | | -| FRAPPE_PATH | Repository URL for Frappe framework source code. Defaults to https://github.com/frappe/frappe | -| FRAPPE_BRANCH | Branch to use for Frappe framework. Defaults to version-15 | -| **Custom Apps** | | -| APPS_JSON_BASE64 | Base64-encoded JSON string from apps.json defining apps to install | -| **Dependencies** | | -| PYTHON_VERSION | Python version for the base image | -| NODE_VERSION | Node.js version | -| WKHTMLTOPDF_VERSION | wkhtmltopdf version | -| **bench only** | | -| DEBIAN_BASE | Debian base version for the bench image, defaults to `bookworm` | -| WKHTMLTOPDF_DISTRO | use the specified distro for debian package. Default is `bookworm` | - -# env file - -The compose file requires several environment variables. You can either export them on your system or create a `.env` file. - -```bash -cp example.env custom.env -``` - -Edit `custom.env` to customize variables for your setup. The template includes common variables, but you can add, modify, or remove any as needed. See [env-variables.md](env-variables.md) for detailed descriptions of all available variables. - -# Creating the final compose file - -Combine the base compose file with appropriate overrides for your use case. This example adds MariaDB, Redis, and exposes ports on `:8080`: - -```bash -docker compose --env-file example.env \ - -f compose.yaml \ - -f overrides/compose.mariadb.yaml \ - -f overrides/compose.redis.yaml \ - -f overrides/compose.noproxy.yaml \ - config > compose.custom.yaml -``` - -This generates `compose.custom.yaml`, which you'll use to start all containers. Customize the overrides and environment variables according to your requirements. - -> **NOTE**: podman compose is just a wrapper, it uses docker-compose if it is available or podman-compose if not. podman-compose have an issue reading .env files ([Issue](https://github.com/containers/podman-compose/issues/475)) and might create an issue when running the containers. - ---- - -**Next:** [Start Setup →](03-start-setup.md) - -**Back:** [Container Overview ←](01-overview.md) +This guide walks you through building Frappe images from the repository resources. + +# Prerequisites + +- git +- docker or podman +- docker compose v2 or podman compose + +> Install containerization software according to the official maintainer documentation. Avoid package managers when not recommended, as they frequently cause compatibility issues. + +# Clone this repo + +```bash +git clone https://github.com/frappe/frappe_docker +cd frappe_docker +``` + +# Define custom apps + +If you dont want to install specific apps to the image skip this section. + +To include custom apps in your image, create an `apps.json` file in the repository root: + +```json +[ + { + "url": "https://github.com/frappe/erpnext", + "branch": "version-15" + }, + { + "url": "https://github.com/frappe/hrms", + "branch": "version-15" + }, + { + "url": "https://github.com/frappe/helpdesk", + "branch": "main" + } +] +``` + +Then generate a base64-encoded string from this file: + +```bash +export APPS_JSON_BASE64=$(base64 -w 0 apps.json) +``` + +# Build the image + +Choose the appropriate build command based on your container runtime and desired image type. This example builds the `layered` image with the custom `apps.json` you created. + +`Docker`: + +```bash +docker build \ + --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ + --build-arg=FRAPPE_BRANCH=version-15 \ + --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \ + --tag=custom:15 \ + --file=images/layered/Containerfile . +``` + +`Podman`: + +```bash +podman build \ + --build-arg=FRAPPE_PATH=https://github.com/frappe/frappe \ + --build-arg=FRAPPE_BRANCH=version-15 \ + --build-arg=APPS_JSON_BASE64=$APPS_JSON_BASE64 \ + --tag=custom:15 \ + --file=images/layered/Containerfile . +``` + +## Build args + +| Arg | Purpose | +| -------------------- | --------------------------------------------------------------------------------------------- | +| **Frappe Framework** | | +| FRAPPE_PATH | Repository URL for Frappe framework source code. Defaults to https://github.com/frappe/frappe | +| FRAPPE_BRANCH | Branch to use for Frappe framework. Defaults to version-15 | +| **Custom Apps** | | +| APPS_JSON_BASE64 | Base64-encoded JSON string from apps.json defining apps to install | +| **Dependencies** | | +| PYTHON_VERSION | Python version for the base image | +| NODE_VERSION | Node.js version | +| WKHTMLTOPDF_VERSION | wkhtmltopdf version | +| **bench only** | | +| DEBIAN_BASE | Debian base version for the bench image, defaults to `bookworm` | +| WKHTMLTOPDF_DISTRO | use the specified distro for debian package. Default is `bookworm` | + +# env file + +The compose file requires several environment variables. You can either export them on your system or create a `.env` file. + +```bash +cp example.env custom.env +``` + +Edit `custom.env` to customize variables for your setup. The template includes common variables, but you can add, modify, or remove any as needed. See [env-variables.md](env-variables.md) for detailed descriptions of all available variables. + +# Creating the final compose file + +Combine the base compose file with appropriate overrides for your use case. This example adds MariaDB, Redis, and exposes ports on `:8080`: + +```bash +docker compose --env-file example.env \ + -f compose.yaml \ + -f overrides/compose.mariadb.yaml \ + -f overrides/compose.redis.yaml \ + -f overrides/compose.noproxy.yaml \ + config > compose.custom.yaml +``` + +This generates `compose.custom.yaml`, which you'll use to start all containers. Customize the overrides and environment variables according to your requirements. + +> **NOTE**: podman compose is just a wrapper, it uses docker-compose if it is available or podman-compose if not. podman-compose have an issue reading .env files ([Issue](https://github.com/containers/podman-compose/issues/475)) and might create an issue when running the containers. + +--- + +**Next:** [Start Setup →](03-start-setup.md) + +**Back:** [Container Overview ←](01-overview.md) diff --git a/docs/container-setup/03-start-setup.md b/docs/container-setup/03-start-setup.md index e19df758..a3c4f67b 100644 --- a/docs/container-setup/03-start-setup.md +++ b/docs/container-setup/03-start-setup.md @@ -1,42 +1,42 @@ -# start Container - -Once your compose file is ready, start all containers with a single command: - -```bash -docker compose -p frappe -f compose.custom.yaml up -d -``` - -```bash -podman-compose --in-pod=1 --project-name frappe -f compose.custom.yaml up -d -``` - -The `-p` (or `--project-name`) flag names the project `frappe`, allowing you to easily reference and manage all containers together. - -# Create a site and install apps - -Frappe is now running, but it's not yet configured. You need to create a site and install your apps. - -```bash -docker compose -p frappe exec backend bench new-site --mariadb-user-host-login-scope='172.%.%.%' -docker compose -p frappe exec backend bench --site install-app erpnext -``` - -```bash -podman exec -ti erpnext_backend_1 /bin/bash -bench new-site --mariadb-user-host-login-scope='172.%.%.%' -bench --site install-app erpnext -``` - -Replace `` with your desired site name. - -> ## Understanding the MariaDB User Scope -> -> The flag --mariadb-user-host-login-scope='172.%.%.%' allows database connections from any IP address within the 172.0.0.0/8 range. This includes all containers and virtual machines running on your machine. -> -> **Why is this necessary?** Docker and Podman assign dynamic IP addresses to containers. If you set a fixed IP address instead, database connections will fail when the container restarts and receives a new IP. The wildcard pattern ensures connections always work, regardless of IP changes. -> -> **Security note:** This scope is sufficient because only the backend container accesses the database. If you need external database access, adjust the scope accordingly, but be cautious with overly permissive settings. - ---- - -**Back:** [Build Setup →](02-build-setup.md) +# start Container + +Once your compose file is ready, start all containers with a single command: + +```bash +docker compose -p frappe -f compose.custom.yaml up -d +``` + +```bash +podman-compose --in-pod=1 --project-name frappe -f compose.custom.yaml up -d +``` + +The `-p` (or `--project-name`) flag names the project `frappe`, allowing you to easily reference and manage all containers together. + +# Create a site and install apps + +Frappe is now running, but it's not yet configured. You need to create a site and install your apps. + +```bash +docker compose -p frappe exec backend bench new-site --mariadb-user-host-login-scope='172.%.%.%' +docker compose -p frappe exec backend bench --site install-app erpnext +``` + +```bash +podman exec -ti erpnext_backend_1 /bin/bash +bench new-site --mariadb-user-host-login-scope='172.%.%.%' +bench --site install-app erpnext +``` + +Replace `` with your desired site name. + +> ## Understanding the MariaDB User Scope +> +> The flag --mariadb-user-host-login-scope='172.%.%.%' allows database connections from any IP address within the 172.0.0.0/8 range. This includes all containers and virtual machines running on your machine. +> +> **Why is this necessary?** Docker and Podman assign dynamic IP addresses to containers. If you set a fixed IP address instead, database connections will fail when the container restarts and receives a new IP. The wildcard pattern ensures connections always work, regardless of IP changes. +> +> **Security note:** This scope is sufficient because only the backend container accesses the database. If you need external database access, adjust the scope accordingly, but be cautious with overly permissive settings. + +--- + +**Back:** [Build Setup →](02-build-setup.md) diff --git a/docs/container-setup/env-variables.md b/docs/container-setup/env-variables.md index 1001c888..58e8ad98 100644 --- a/docs/container-setup/env-variables.md +++ b/docs/container-setup/env-variables.md @@ -1,112 +1,112 @@ -# Environment Variables Reference - -Environment variables configure your Frappe Docker setup. They can be set directly in the container or defined in a `.env` file referenced by Docker Compose. - -**Getting Started:** - -```bash -cp example.env .env -``` - -Then edit `.env` and set variables according to your needs. - ---- - -## Required Variables - -| Variable | Purpose | Example | Notes | -| ----------------- | ------------------------------------------------ | -------------------------------- | ---------------------------------------------------------------- | -| `FRAPPE_PATH` | Frappe framework path | https://github.com/frappe/frappe | | -| `FRAPPE_BRANCH` | Frappe Branch | `version-15` | See [Frappe releases](https://github.com/frappe/frappe/releases) | -| `ERPNEXT_VERSION` | ERPNext release version | `v15.67.0` | Required although its never used | -| `DB_PASSWORD` | Password for database root (MariaDB or Postgres) | `secure_password_123` | Not needed if using `DB_PASSWORD_SECRETS_FILE` | - ---- - -## Database Configuration - -| Variable | Purpose | Default | When to Set | -| -------------------------- | ----------------------------------------- | ------------------------------------ | ---------------------------------- | -| `DB_PASSWORD` | Database root user password | 123 | Always (unless using secrets file) | -| `DB_PASSWORD_SECRETS_FILE` | Path to file containing database password | — | Setup mariadb-secrets overrider | -| `DB_HOST` | Database hostname or IP | `db` (service name) | Only if using external database | -| `DB_PORT` | Database port | `3306` (MariaDB) / `5432` (Postgres) | Only if using external database | - ---- - -## Redis Configuration - -| Variable | Purpose | Default | When to Set | -| ------------- | --------------------------------------------------- | ---------------------------- | ------------------------------------- | -| `REDIS_CACHE` | Redis hostname for caching | `redis-cache` (service name) | Only if using external Redis instance | -| `REDIS_QUEUE` | Redis hostname for job queues and real-time updates | `redis-queue` (service name) | Only if using external Redis instance | - ---- - -## HTTPS & SSL Configuration - -| Variable | Purpose | Default | When to Set | -| ------------------- | ------------------------------------------------ | ------- | ---------------------------------------- | -| `LETSENCRYPT_EMAIL` | Email for Let's Encrypt certificate registration | — | Required if using HTTPS override | -| `SITES` | List of domains for SSL certificates | — | Required if using reverse proxy override | - -**Format for `SITES`:** - -```bash -# Single site -SITES=`mysite.example.com` - -# Wildcard (any subdomain) -SITES=`{any:.+}` -``` - ---- - -## Site Configuration - -| Variable | Purpose | Default | When to Set | -| ------------------------- | -------------------------------- | ---------------------------------------- | ----------------------------------------------- | -| `FRAPPE_SITE_NAME_HEADER` | Site name for multi-tenant setup | `$host` (resolved from request hostname) | When accessing by IP or need explicit site name | - -**Examples:** - -If your site is named `mysite` but you want to access it via `127.0.0.1`: - -```bash -FRAPPE_SITE_NAME_HEADER=mysite -``` - -If your site is named `example.com` and you access it via that domain, no need to set this (defaults to hostname). - ---- - -## Image Configuration - -| Variable | Purpose | Default | Notes | -| ---------------- | ------------------------------ | --------------------- | ------------------------------------------------------- | -| `CUSTOM_IMAGE` | Custom Docker image repository | Frappe official image | Leave empty to use default | -| `CUSTOM_TAG` | Custom Docker image tag | Latest stable | Corresponds to `FRAPPE_VERSION` | -| `PULL_POLICY` | Image pull behavior | `always` | Options: `always`, `never`, `if-not-present` | -| `RESTART_POLICY` | Container restart behavior | `unless-stopped` | Options: `no`, `always`, `unless-stopped`, `on-failure` | - ---- - -## Nginx Proxy Configuration - -| Variable | Purpose | Default | Allowed Values | -| ---------------------- | ---------------------------------- | -------------- | -------------------------------------------- | -| `BACKEND` | Backend service address and port | `0.0.0.0:8000` | `{host}:{port}` | -| `SOCKETIO` | Socket.IO service address and port | `0.0.0.0:9000` | `{host}:{port}` | -| `HTTP_PUBLISH_PORT` | Published HTTP port | `8080` | Any available port | -| `PROXY_READ_TIMEOUT` | Upstream request timeout | `120s` | Any nginx timeout value (e.g., `300s`, `5m`) | -| `CLIENT_MAX_BODY_SIZE` | Maximum upload file size | `50m` | Any nginx size value (e.g., `100m`, `1g`) | - -### Real IP Configuration (Behind Proxy) - -Use these variables when running behind a reverse proxy or load balancer: - -| Variable | Purpose | Default | -| ---------------------------- | ------------------------------------------------- | ----------------- | -| `UPSTREAM_REAL_IP_ADDRESS` | Trusted upstream IP address for real IP detection | `127.0.0.1` | -| `UPSTREAM_REAL_IP_HEADER` | Request header containing client IP | `X-Forwarded-For` | -| `UPSTREAM_REAL_IP_RECURSIVE` | Enable recursive IP search | `off` | +# Environment Variables Reference + +Environment variables configure your Frappe Docker setup. They can be set directly in the container or defined in a `.env` file referenced by Docker Compose. + +**Getting Started:** + +```bash +cp example.env .env +``` + +Then edit `.env` and set variables according to your needs. + +--- + +## Required Variables + +| Variable | Purpose | Example | Notes | +| ----------------- | ------------------------------------------------ | -------------------------------- | ---------------------------------------------------------------- | +| `FRAPPE_PATH` | Frappe framework path | https://github.com/frappe/frappe | | +| `FRAPPE_BRANCH` | Frappe Branch | `version-15` | See [Frappe releases](https://github.com/frappe/frappe/releases) | +| `ERPNEXT_VERSION` | ERPNext release version | `v15.67.0` | Required although its never used | +| `DB_PASSWORD` | Password for database root (MariaDB or Postgres) | `secure_password_123` | Not needed if using `DB_PASSWORD_SECRETS_FILE` | + +--- + +## Database Configuration + +| Variable | Purpose | Default | When to Set | +| -------------------------- | ----------------------------------------- | ------------------------------------ | ---------------------------------- | +| `DB_PASSWORD` | Database root user password | 123 | Always (unless using secrets file) | +| `DB_PASSWORD_SECRETS_FILE` | Path to file containing database password | — | Setup mariadb-secrets overrider | +| `DB_HOST` | Database hostname or IP | `db` (service name) | Only if using external database | +| `DB_PORT` | Database port | `3306` (MariaDB) / `5432` (Postgres) | Only if using external database | + +--- + +## Redis Configuration + +| Variable | Purpose | Default | When to Set | +| ------------- | --------------------------------------------------- | ---------------------------- | ------------------------------------- | +| `REDIS_CACHE` | Redis hostname for caching | `redis-cache` (service name) | Only if using external Redis instance | +| `REDIS_QUEUE` | Redis hostname for job queues and real-time updates | `redis-queue` (service name) | Only if using external Redis instance | + +--- + +## HTTPS & SSL Configuration + +| Variable | Purpose | Default | When to Set | +| ------------------- | ------------------------------------------------ | ------- | ---------------------------------------- | +| `LETSENCRYPT_EMAIL` | Email for Let's Encrypt certificate registration | — | Required if using HTTPS override | +| `SITES` | List of domains for SSL certificates | — | Required if using reverse proxy override | + +**Format for `SITES`:** + +```bash +# Single site +SITES=`mysite.example.com` + +# Wildcard (any subdomain) +SITES=`{any:.+}` +``` + +--- + +## Site Configuration + +| Variable | Purpose | Default | When to Set | +| ------------------------- | -------------------------------- | ---------------------------------------- | ----------------------------------------------- | +| `FRAPPE_SITE_NAME_HEADER` | Site name for multi-tenant setup | `$host` (resolved from request hostname) | When accessing by IP or need explicit site name | + +**Examples:** + +If your site is named `mysite` but you want to access it via `127.0.0.1`: + +```bash +FRAPPE_SITE_NAME_HEADER=mysite +``` + +If your site is named `example.com` and you access it via that domain, no need to set this (defaults to hostname). + +--- + +## Image Configuration + +| Variable | Purpose | Default | Notes | +| ---------------- | ------------------------------ | --------------------- | ------------------------------------------------------- | +| `CUSTOM_IMAGE` | Custom Docker image repository | Frappe official image | Leave empty to use default | +| `CUSTOM_TAG` | Custom Docker image tag | Latest stable | Corresponds to `FRAPPE_VERSION` | +| `PULL_POLICY` | Image pull behavior | `always` | Options: `always`, `never`, `if-not-present` | +| `RESTART_POLICY` | Container restart behavior | `unless-stopped` | Options: `no`, `always`, `unless-stopped`, `on-failure` | + +--- + +## Nginx Proxy Configuration + +| Variable | Purpose | Default | Allowed Values | +| ---------------------- | ---------------------------------- | -------------- | -------------------------------------------- | +| `BACKEND` | Backend service address and port | `0.0.0.0:8000` | `{host}:{port}` | +| `SOCKETIO` | Socket.IO service address and port | `0.0.0.0:9000` | `{host}:{port}` | +| `HTTP_PUBLISH_PORT` | Published HTTP port | `8080` | Any available port | +| `PROXY_READ_TIMEOUT` | Upstream request timeout | `120s` | Any nginx timeout value (e.g., `300s`, `5m`) | +| `CLIENT_MAX_BODY_SIZE` | Maximum upload file size | `50m` | Any nginx size value (e.g., `100m`, `1g`) | + +### Real IP Configuration (Behind Proxy) + +Use these variables when running behind a reverse proxy or load balancer: + +| Variable | Purpose | Default | +| ---------------------------- | ------------------------------------------------- | ----------------- | +| `UPSTREAM_REAL_IP_ADDRESS` | Trusted upstream IP address for real IP detection | `127.0.0.1` | +| `UPSTREAM_REAL_IP_HEADER` | Request header containing client IP | `X-Forwarded-For` | +| `UPSTREAM_REAL_IP_RECURSIVE` | Enable recursive IP search | `off` | diff --git a/docs/container-setup/overrider.md b/docs/container-setup/overrider.md index 9188489d..40b36720 100644 --- a/docs/container-setup/overrider.md +++ b/docs/container-setup/overrider.md @@ -1,27 +1,27 @@ -Overrides extend the base compose.yaml with additional services or modify existing behavior. Include them in your compose command using multiple -f flags. - -```bash -docker compose -f compose.yaml -f overrides/compose.mariadb.yaml -f overrides/compose.redis.yaml config > compose.custom.yaml -``` - -| Overrider | Purpose | Additional Info | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -| **Database** | | | -| compose.mariadb.yaml | Adds MariaDB database service | set `DB_PASSWORD` or default Password will be used | -| compose.mariadb-secrets.yaml | Adds MariaDB with password from a secret file instead of environment variable | Set `DB_PASSWORD_SECRETS_FILE` to the path of your secret file | -| compose.mariadb-shared.yaml | Makes MariaDB available on a shared network (mariadb-network) for other services | set `DB_PASSWORD` | -| compose.postgres.yaml | Uses PostgreSQL instead of MariaDB as the database | set `DB_PASSWORD` | -| **Proxy** | | | -| compose.noproxy.yaml | Exposes the application directly on port `:8080` without a reverse proxy | | -| compose.proxy.yaml | Uses Traefik as HTTP reverse proxy on port `:80` | You can change the published port by setting `HTTP_PUBLISH_PORT` | -| compose.https.yaml | Uses Traefik as HTTPS reverse proxy on Port `:443` with automatic HTTP-to-HTTPS redirect | `SITES` and `LETSENCRYPT_EMAIL` must be set. `HTTP_PUBLISH_PORT` and `HTTPS_PUBLISH_PORT` can be set. | -| **Redis** | | | -| compose.redis.yaml | Adds Redis service for caching and background job queuing | -| **TBD** | **The following overrides are available but lack documentation. If you use them and understand their purpose, please consider contributing to this documentation.** | -| compose.backup-cron.yaml | | | -| compose.custom-domain-ssl.yaml | | | -| compose.custom-domain.yaml | | | -| compose.multi-bench-ssl.yaml | | | -| compose.multi-bench.yaml | | | -| compose.traefik-ssl.yaml | | | -| compose.traefik.yaml | | | +Overrides extend the base compose.yaml with additional services or modify existing behavior. Include them in your compose command using multiple -f flags. + +```bash +docker compose -f compose.yaml -f overrides/compose.mariadb.yaml -f overrides/compose.redis.yaml config > compose.custom.yaml +``` + +| Overrider | Purpose | Additional Info | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| **Database** | | | +| compose.mariadb.yaml | Adds MariaDB database service | set `DB_PASSWORD` or default Password will be used | +| compose.mariadb-secrets.yaml | Adds MariaDB with password from a secret file instead of environment variable | Set `DB_PASSWORD_SECRETS_FILE` to the path of your secret file | +| compose.mariadb-shared.yaml | Makes MariaDB available on a shared network (mariadb-network) for other services | set `DB_PASSWORD` | +| compose.postgres.yaml | Uses PostgreSQL instead of MariaDB as the database | set `DB_PASSWORD` | +| **Proxy** | | | +| compose.noproxy.yaml | Exposes the application directly on port `:8080` without a reverse proxy | | +| compose.proxy.yaml | Uses Traefik as HTTP reverse proxy on port `:80` | You can change the published port by setting `HTTP_PUBLISH_PORT` | +| compose.https.yaml | Uses Traefik as HTTPS reverse proxy on Port `:443` with automatic HTTP-to-HTTPS redirect | `SITES` and `LETSENCRYPT_EMAIL` must be set. `HTTP_PUBLISH_PORT` and `HTTPS_PUBLISH_PORT` can be set. | +| **Redis** | | | +| compose.redis.yaml | Adds Redis service for caching and background job queuing | +| **TBD** | **The following overrides are available but lack documentation. If you use them and understand their purpose, please consider contributing to this documentation.** | +| compose.backup-cron.yaml | | | +| compose.custom-domain-ssl.yaml | | | +| compose.custom-domain.yaml | | | +| compose.multi-bench-ssl.yaml | | | +| compose.multi-bench.yaml | | | +| compose.traefik-ssl.yaml | | | +| compose.traefik.yaml | | | diff --git a/docs/development.md b/docs/development.md index 8bdeb4f8..9fe30934 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,422 +1,422 @@ -# Getting Started - -## Prerequisites - -In order to start developing you need to satisfy the following prerequisites: - -- Docker -- docker-compose -- user added to docker group - -It is recommended you allocate at least 4GB of RAM to docker: - -- [Instructions for Windows](https://docs.docker.com/docker-for-windows/#resources) -- [Instructions for macOS](https://docs.docker.com/desktop/settings/mac/#advanced) - -Here is a screenshot showing the relevant setting in the Help Manual -![image](images/Docker%20Manual%20Screenshot%20-%20Resources%20section.png) -Here is a screenshot showing the settings in Docker Desktop on Mac -![images](images/Docker%20Desktop%20Screenshot%20-%20Resources%20section.png) - -## Bootstrap Containers for development - -Clone and change directory to frappe_docker directory - -```shell -git clone https://github.com/frappe/frappe_docker.git -cd frappe_docker -``` - -Copy example devcontainer config from `devcontainer-example` to `.devcontainer` - -```shell -cp -R devcontainer-example .devcontainer -``` - -Copy example vscode config for devcontainer from `development/vscode-example` to `development/.vscode`. This will setup basic configuration for debugging. - -```shell -cp -R development/vscode-example development/.vscode -``` - -## Use VSCode Remote Containers extension - -For most people getting started with Frappe development, the best solution is to use [VSCode Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). - -Before opening the folder in container, determine the database that you want to use. The default is MariaDB. -If you want to use PostgreSQL instead, edit `.devcontainer/docker-compose.yml` and uncomment the section for `postgresql` service, and you may also want to comment `mariadb` as well. - -VSCode should automatically inquire you to install the required extensions, that can also be installed manually as follows: - -- Install Dev Containers for VSCode - - through command line `code --install-extension ms-vscode-remote.remote-containers` - - clicking on the Install button in the Vistual Studio Marketplace: [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) - - View: Extensions command in VSCode (Windows: Ctrl+Shift+X; macOS: Cmd+Shift+X) then search for extension `ms-vscode-remote.remote-containers` - -After the extensions are installed, you can: - -- Open frappe_docker folder in VS Code. - - `code .` -- Launch the command, from Command Palette (Ctrl + Shift + P) `Dev Containers: Reopen in Container`. You can also click in the bottom left corner to access the remote container menu. - -Notes: - -- The `development` directory is ignored by git. It is mounted and available inside the container. Create all your benches (installations of bench, the tool that manages frappe) inside this directory. -- Node v14 and v10 are installed. Check with `nvm ls`. Node v14 is used by default. - -### Setup first bench - -> Jump to [scripts](#setup-bench--new-site-using-script) section to setup a bench automatically. Alternatively, you can setup a bench manually using below guide. - -Run the following commands in the terminal inside the container. You might need to create a new terminal in VSCode. - -NOTE: Prior to doing the following, make sure the user is **frappe**. - -```shell -bench init --skip-redis-config-generation frappe-bench -cd frappe-bench -``` - -To setup frappe framework version 14 bench set `PYENV_VERSION` environment variable to `3.10.5` (default) and use NodeJS version 16 (default), - -```shell -# Use default environments -bench init --skip-redis-config-generation --frappe-branch version-14 frappe-bench -# Or set environment versions explicitly -nvm use v16 -PYENV_VERSION=3.10.13 bench init --skip-redis-config-generation --frappe-branch version-14 frappe-bench -# Switch directory -cd frappe-bench -``` - -To setup frappe framework version 13 bench set `PYENV_VERSION` environment variable to `3.9.17` and use NodeJS version 14, - -```shell -nvm use v14 -PYENV_VERSION=3.9.17 bench init --skip-redis-config-generation --frappe-branch version-13 frappe-bench -cd frappe-bench -``` - -### Setup hosts - -We need to tell bench to use the right containers instead of localhost. Run the following commands inside the container: - -```shell -bench set-config -g db_host mariadb -bench set-config -g redis_cache redis://redis-cache:6379 -bench set-config -g redis_queue redis://redis-queue:6379 -bench set-config -g redis_socketio redis://redis-queue:6379 -``` - -For any reason the above commands fail, set the values in `common_site_config.json` manually. - -```json -{ - "db_host": "mariadb", - "redis_cache": "redis://redis-cache:6379", - "redis_queue": "redis://redis-queue:6379", - "redis_socketio": "redis://redis-queue:6379" -} -``` - -### Edit Honcho's Procfile - -Note : With the option '--skip-redis-config-generation' during bench init, these actions are no more needed. But at least, take a look to ProcFile to see what going on when bench launch honcho on start command - -Honcho is the tool used by Bench to manage all the processes Frappe requires. Usually, these all run in localhost, but in this case, we have external containers for Redis. For this reason, we have to stop Honcho from trying to start Redis processes. - -Honcho is installed in global python environment along with bench. To make it available locally you've to install it in every `frappe-bench/env` you create. Install it using command `./env/bin/pip install honcho`. It is required locally if you wish to use is as part of VSCode launch configuration. - -Open the Procfile file and remove the three lines containing the configuration from Redis, either by editing manually the file: - -```shell -code Procfile -``` - -Or running the following command: - -```shell -sed -i '/redis/d' ./Procfile -``` - -### Create a new site with bench - -You can create a new site with the following command: - -```shell -bench new-site --mariadb-user-host-login-scope=% sitename -``` - -sitename MUST end with .localhost for trying deployments locally. - -for example: - -```shell -bench new-site --mariadb-user-host-login-scope=% development.localhost -``` - -The same command can be run non-interactively as well: - -```shell -bench new-site --db-root-password 123 --admin-password admin --mariadb-user-host-login-scope=% development.localhost -``` - -The command will ask the MariaDB root password. The default root password is `123`. -This will create a new site and a `development.localhost` directory under `frappe-bench/sites`. -The option `--mariadb-user-host-login-scope=%` will configure site's database credentials to work with docker. -You may need to configure your system /etc/hosts if you're on Linux, Mac, or its Windows equivalent. - -To setup site with PostgreSQL as database use option `--db-type postgres` and `--db-host postgresql`. (Available only v12 onwards, currently NOT available for ERPNext). - -Example: - -```shell -bench new-site --db-type postgres --db-host postgresql mypgsql.localhost -``` - -To avoid entering postgresql username and root password, set it in `common_site_config.json`, - -```shell -bench config set-common-config -c root_login postgres -bench config set-common-config -c root_password '"123"' -``` - -Note: If PostgreSQL is not required, the postgresql service / container can be stopped. - -### Set bench developer mode on the new site - -To develop a new app, the last step will be setting the site into developer mode. Documentation is available at [this link](https://frappe.io/docs/user/en/guides/app-development/how-enable-developer-mode-in-frappe). - -```shell -bench --site development.localhost set-config developer_mode 1 -bench --site development.localhost clear-cache -``` - -### Install an app - -To install an app we need to fetch it from the appropriate git repo, then install in on the appropriate site: - -You can check [VSCode container remote extension documentation](https://code.visualstudio.com/docs/remote/containers#_sharing-git-credentials-with-your-container) regarding git credential sharing. - -To install custom app - -```shell -# --branch is optional, use it to point to branch on custom app repository -bench get-app --branch version-12 https://github.com/myusername/myapp -bench --site development.localhost install-app myapp -``` - -At the time of this writing, the Payments app has been factored out of the Version 14 ERPNext app and is now a separate app. ERPNext will not install it. - -```shell -bench get-app --branch version-14 --resolve-deps erpnext -bench --site development.localhost install-app erpnext -``` - -To install ERPNext (from the version-13 branch): - -```shell -bench get-app --branch version-13 erpnext -bench --site development.localhost install-app erpnext -``` - -Note: Both frappe and erpnext must be on branch with same name. e.g. version-14 -You can use the `switch-to-branch` command to align versions if you get an error about mismatching versions. - -```shell -bench switch-to-branch version-xx -``` - -### Start Frappe without debugging - -Execute following command from the `frappe-bench` directory. - -```shell -bench start -``` - -You can now login with user `Administrator` and the password you choose when creating the site. -Your website will now be accessible at location [development.localhost:8000](http://development.localhost:8000) -Note: To start bench with debugger refer section for debugging. - -### Setup bench / new site using script - -Most developers work with numerous clients and versions. Moreover, apps may be required to be installed by everyone on the team working for a client. - -This is simplified using a script to automate the process of creating a new bench / site and installing the required apps. The `Administrator` password for created sites is `admin`. - -Sample `apps-example.json` is used by default, it installs erpnext on current stable release. To install custom apps, copy the `apps-example.json` to custom json file and make changes to list of apps. Pass this file to the `installer.py` script. - -> You may have apps in private repos which may require ssh access. You may use SSH from your home directory on linux (configurable in docker-compose.yml). - -```shell -python installer.py #pass --db-type postgres for postgresdb -``` - -For command help - -```shell -python installer.py --help -usage: installer.py [-h] [-j APPS_JSON] [-b BENCH_NAME] [-s SITE_NAME] [-r FRAPPE_REPO] [-t FRAPPE_BRANCH] [-p PY_VERSION] [-n NODE_VERSION] [-v] [-a ADMIN_PASSWORD] [-d DB_TYPE] - -options: - -h, --help show this help message and exit - -j APPS_JSON, --apps-json APPS_JSON - Path to apps.json, default: apps-example.json - -b BENCH_NAME, --bench-name BENCH_NAME - Bench directory name, default: frappe-bench - -s SITE_NAME, --site-name SITE_NAME - Site name, should end with .localhost, default: development.localhost - -r FRAPPE_REPO, --frappe-repo FRAPPE_REPO - frappe repo to use, default: https://github.com/frappe/frappe - -t FRAPPE_BRANCH, --frappe-branch FRAPPE_BRANCH - frappe repo to use, default: version-15 - -p PY_VERSION, --py-version PY_VERSION - python version, default: Not Set - -n NODE_VERSION, --node-version NODE_VERSION - node version, default: Not Set - -v, --verbose verbose output - -a ADMIN_PASSWORD, --admin-password ADMIN_PASSWORD - admin password for site, default: admin - -d DB_TYPE, --db-type DB_TYPE - Database type to use (e.g., mariadb or postgres) -``` - -A new bench and / or site is created for the client with following defaults. - -- MariaDB root password: `123` -- Admin password: `admin` - -> To use Postegres DB, comment the mariabdb service and uncomment postegres service. - -### Start Frappe with Visual Studio Code Python Debugging - -To enable Python debugging inside Visual Studio Code, you must first install the `ms-python.python` extension inside the container. This should have already happened automatically, but depending on your VSCode config, you can force it by: - -- Click on the extension icon inside VSCode -- Search `ms-python.python` -- Click on `Install on Dev Container: Frappe Bench` -- Click on 'Reload' - -We need to start bench separately through the VSCode debugger. For this reason, **instead** of running `bench start` you should run the following command inside the frappe-bench directory: - -```shell -honcho start \ - socketio \ - watch \ - schedule \ - worker_short \ - worker_long -``` - -Alternatively you can use the VSCode launch configuration "Honcho SocketIO Watch Schedule Worker" which launches the same command as above. - -This command starts all processes with the exception of Redis (which is already running in separate container) and the `web` process. The latter can can finally be started from the debugger tab of VSCode by clicking on the "play" button. - -You can now login with user `Administrator` and the password you choose when creating the site, if you followed this guide's unattended install that password is going to be `admin`. - -To debug workers, skip starting worker with honcho and start it with VSCode debugger. - -For advance vscode configuration in the devcontainer, change the config files in `development/.vscode`. - -## Developing using the interactive console - -You can launch a simple interactive shell console in the terminal with: - -```shell -bench --site development.localhost console -``` - -More likely, you may want to launch VSCode interactive console based on Jupyter kernel. - -Launch VSCode command palette (cmd+shift+p or ctrl+shift+p), run the command `Python: Select interpreter to start Jupyter server` and select `/workspace/development/frappe-bench/env/bin/python`. - -The first step is installing and updating the required software. Usually the frappe framework may require an older version of Jupyter, while VSCode likes to move fast, this can [cause issues](https://github.com/jupyter/jupyter_console/issues/158). For this reason we need to run the following command. - -```shell -/workspace/development/frappe-bench/env/bin/python -m pip install --upgrade jupyter ipykernel ipython -``` - -Then, run the command `Python: Show Python interactive window` from the VSCode command palette. - -Replace `development.localhost` with your site and run the following code in a Jupyter cell: - -```python -import frappe - -frappe.init(site='development.localhost', sites_path='/workspace/development/frappe-bench/sites') -frappe.connect() -frappe.local.lang = frappe.db.get_default('lang') -frappe.db.connect() -``` - -The first command can take a few seconds to be executed, this is to be expected. - -## Manually start containers - -In case you don't use VSCode, you may start the containers manually with the following command: - -### Running the containers - -```shell -docker-compose -f .devcontainer/docker-compose.yml up -d -``` - -And enter the interactive shell for the development container with the following command: - -```shell -docker exec -e "TERM=xterm-256color" -w /workspace/development -it devcontainer-frappe-1 bash -``` - -## Use additional services during development - -Add any service that is needed for development in the `.devcontainer/docker-compose.yml` then rebuild and reopen in devcontainer. - -e.g. - -```yaml -... -services: - ... - postgresql: - image: postgres:11.8 - environment: - POSTGRES_PASSWORD: 123 - volumes: - - postgresql-data:/var/lib/postgresql/data - ports: - - 5432:5432 - -volumes: - ... - postgresql-data: -``` - -Access the service by service name from the `frappe` development container. The above service will be accessible via hostname `postgresql`. If ports are published on to host, access it via `localhost:5432`. - -## Using Cypress UI tests - -To run cypress based UI tests in a docker environment, follow the below steps: - -1. Install and setup X11 tooling on VM using the script `install_x11_deps.sh` - -```shell - sudo bash ./install_x11_deps.sh -``` - -This script will install required deps, enable X11Forwarding and restart SSH daemon and export `DISPLAY` variable. - -2. Run X11 service `startx` or `xquartz` -3. Start docker compose services. -4. SSH into ui-tester service using `docker exec..` command -5. Export CYPRESS_baseUrl and other required env variables -6. Start Cypress UI console by issuing `cypress run command` - -> More references : [Cypress Official Documentation](https://www.cypress.io/blog/2019/05/02/run-cypress-with-a-single-docker-command) - -> Ensure DISPLAY environment is always exported. - -## Using Mailpit to test mail services - -To use Mailpit just uncomment the service in the docker-compose.yml file. -The Interface is then available under port 8025 and the smtp service can be used as mailpit:1025. +# Getting Started + +## Prerequisites + +In order to start developing you need to satisfy the following prerequisites: + +- Docker +- docker-compose +- user added to docker group + +It is recommended you allocate at least 4GB of RAM to docker: + +- [Instructions for Windows](https://docs.docker.com/docker-for-windows/#resources) +- [Instructions for macOS](https://docs.docker.com/desktop/settings/mac/#advanced) + +Here is a screenshot showing the relevant setting in the Help Manual +![image](images/Docker%20Manual%20Screenshot%20-%20Resources%20section.png) +Here is a screenshot showing the settings in Docker Desktop on Mac +![images](images/Docker%20Desktop%20Screenshot%20-%20Resources%20section.png) + +## Bootstrap Containers for development + +Clone and change directory to frappe_docker directory + +```shell +git clone https://github.com/frappe/frappe_docker.git +cd frappe_docker +``` + +Copy example devcontainer config from `devcontainer-example` to `.devcontainer` + +```shell +cp -R devcontainer-example .devcontainer +``` + +Copy example vscode config for devcontainer from `development/vscode-example` to `development/.vscode`. This will setup basic configuration for debugging. + +```shell +cp -R development/vscode-example development/.vscode +``` + +## Use VSCode Remote Containers extension + +For most people getting started with Frappe development, the best solution is to use [VSCode Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). + +Before opening the folder in container, determine the database that you want to use. The default is MariaDB. +If you want to use PostgreSQL instead, edit `.devcontainer/docker-compose.yml` and uncomment the section for `postgresql` service, and you may also want to comment `mariadb` as well. + +VSCode should automatically inquire you to install the required extensions, that can also be installed manually as follows: + +- Install Dev Containers for VSCode + - through command line `code --install-extension ms-vscode-remote.remote-containers` + - clicking on the Install button in the Vistual Studio Marketplace: [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) + - View: Extensions command in VSCode (Windows: Ctrl+Shift+X; macOS: Cmd+Shift+X) then search for extension `ms-vscode-remote.remote-containers` + +After the extensions are installed, you can: + +- Open frappe_docker folder in VS Code. + - `code .` +- Launch the command, from Command Palette (Ctrl + Shift + P) `Dev Containers: Reopen in Container`. You can also click in the bottom left corner to access the remote container menu. + +Notes: + +- The `development` directory is ignored by git. It is mounted and available inside the container. Create all your benches (installations of bench, the tool that manages frappe) inside this directory. +- Node v14 and v10 are installed. Check with `nvm ls`. Node v14 is used by default. + +### Setup first bench + +> Jump to [scripts](#setup-bench--new-site-using-script) section to setup a bench automatically. Alternatively, you can setup a bench manually using below guide. + +Run the following commands in the terminal inside the container. You might need to create a new terminal in VSCode. + +NOTE: Prior to doing the following, make sure the user is **frappe**. + +```shell +bench init --skip-redis-config-generation frappe-bench +cd frappe-bench +``` + +To setup frappe framework version 14 bench set `PYENV_VERSION` environment variable to `3.10.5` (default) and use NodeJS version 16 (default), + +```shell +# Use default environments +bench init --skip-redis-config-generation --frappe-branch version-14 frappe-bench +# Or set environment versions explicitly +nvm use v16 +PYENV_VERSION=3.10.13 bench init --skip-redis-config-generation --frappe-branch version-14 frappe-bench +# Switch directory +cd frappe-bench +``` + +To setup frappe framework version 13 bench set `PYENV_VERSION` environment variable to `3.9.17` and use NodeJS version 14, + +```shell +nvm use v14 +PYENV_VERSION=3.9.17 bench init --skip-redis-config-generation --frappe-branch version-13 frappe-bench +cd frappe-bench +``` + +### Setup hosts + +We need to tell bench to use the right containers instead of localhost. Run the following commands inside the container: + +```shell +bench set-config -g db_host mariadb +bench set-config -g redis_cache redis://redis-cache:6379 +bench set-config -g redis_queue redis://redis-queue:6379 +bench set-config -g redis_socketio redis://redis-queue:6379 +``` + +For any reason the above commands fail, set the values in `common_site_config.json` manually. + +```json +{ + "db_host": "mariadb", + "redis_cache": "redis://redis-cache:6379", + "redis_queue": "redis://redis-queue:6379", + "redis_socketio": "redis://redis-queue:6379" +} +``` + +### Edit Honcho's Procfile + +Note : With the option '--skip-redis-config-generation' during bench init, these actions are no more needed. But at least, take a look to ProcFile to see what going on when bench launch honcho on start command + +Honcho is the tool used by Bench to manage all the processes Frappe requires. Usually, these all run in localhost, but in this case, we have external containers for Redis. For this reason, we have to stop Honcho from trying to start Redis processes. + +Honcho is installed in global python environment along with bench. To make it available locally you've to install it in every `frappe-bench/env` you create. Install it using command `./env/bin/pip install honcho`. It is required locally if you wish to use is as part of VSCode launch configuration. + +Open the Procfile file and remove the three lines containing the configuration from Redis, either by editing manually the file: + +```shell +code Procfile +``` + +Or running the following command: + +```shell +sed -i '/redis/d' ./Procfile +``` + +### Create a new site with bench + +You can create a new site with the following command: + +```shell +bench new-site --mariadb-user-host-login-scope=% sitename +``` + +sitename MUST end with .localhost for trying deployments locally. + +for example: + +```shell +bench new-site --mariadb-user-host-login-scope=% development.localhost +``` + +The same command can be run non-interactively as well: + +```shell +bench new-site --db-root-password 123 --admin-password admin --mariadb-user-host-login-scope=% development.localhost +``` + +The command will ask the MariaDB root password. The default root password is `123`. +This will create a new site and a `development.localhost` directory under `frappe-bench/sites`. +The option `--mariadb-user-host-login-scope=%` will configure site's database credentials to work with docker. +You may need to configure your system /etc/hosts if you're on Linux, Mac, or its Windows equivalent. + +To setup site with PostgreSQL as database use option `--db-type postgres` and `--db-host postgresql`. (Available only v12 onwards, currently NOT available for ERPNext). + +Example: + +```shell +bench new-site --db-type postgres --db-host postgresql mypgsql.localhost +``` + +To avoid entering postgresql username and root password, set it in `common_site_config.json`, + +```shell +bench config set-common-config -c root_login postgres +bench config set-common-config -c root_password '"123"' +``` + +Note: If PostgreSQL is not required, the postgresql service / container can be stopped. + +### Set bench developer mode on the new site + +To develop a new app, the last step will be setting the site into developer mode. Documentation is available at [this link](https://frappe.io/docs/user/en/guides/app-development/how-enable-developer-mode-in-frappe). + +```shell +bench --site development.localhost set-config developer_mode 1 +bench --site development.localhost clear-cache +``` + +### Install an app + +To install an app we need to fetch it from the appropriate git repo, then install in on the appropriate site: + +You can check [VSCode container remote extension documentation](https://code.visualstudio.com/docs/remote/containers#_sharing-git-credentials-with-your-container) regarding git credential sharing. + +To install custom app + +```shell +# --branch is optional, use it to point to branch on custom app repository +bench get-app --branch version-12 https://github.com/myusername/myapp +bench --site development.localhost install-app myapp +``` + +At the time of this writing, the Payments app has been factored out of the Version 14 ERPNext app and is now a separate app. ERPNext will not install it. + +```shell +bench get-app --branch version-14 --resolve-deps erpnext +bench --site development.localhost install-app erpnext +``` + +To install ERPNext (from the version-13 branch): + +```shell +bench get-app --branch version-13 erpnext +bench --site development.localhost install-app erpnext +``` + +Note: Both frappe and erpnext must be on branch with same name. e.g. version-14 +You can use the `switch-to-branch` command to align versions if you get an error about mismatching versions. + +```shell +bench switch-to-branch version-xx +``` + +### Start Frappe without debugging + +Execute following command from the `frappe-bench` directory. + +```shell +bench start +``` + +You can now login with user `Administrator` and the password you choose when creating the site. +Your website will now be accessible at location [development.localhost:8000](http://development.localhost:8000) +Note: To start bench with debugger refer section for debugging. + +### Setup bench / new site using script + +Most developers work with numerous clients and versions. Moreover, apps may be required to be installed by everyone on the team working for a client. + +This is simplified using a script to automate the process of creating a new bench / site and installing the required apps. The `Administrator` password for created sites is `admin`. + +Sample `apps-example.json` is used by default, it installs erpnext on current stable release. To install custom apps, copy the `apps-example.json` to custom json file and make changes to list of apps. Pass this file to the `installer.py` script. + +> You may have apps in private repos which may require ssh access. You may use SSH from your home directory on linux (configurable in docker-compose.yml). + +```shell +python installer.py #pass --db-type postgres for postgresdb +``` + +For command help + +```shell +python installer.py --help +usage: installer.py [-h] [-j APPS_JSON] [-b BENCH_NAME] [-s SITE_NAME] [-r FRAPPE_REPO] [-t FRAPPE_BRANCH] [-p PY_VERSION] [-n NODE_VERSION] [-v] [-a ADMIN_PASSWORD] [-d DB_TYPE] + +options: + -h, --help show this help message and exit + -j APPS_JSON, --apps-json APPS_JSON + Path to apps.json, default: apps-example.json + -b BENCH_NAME, --bench-name BENCH_NAME + Bench directory name, default: frappe-bench + -s SITE_NAME, --site-name SITE_NAME + Site name, should end with .localhost, default: development.localhost + -r FRAPPE_REPO, --frappe-repo FRAPPE_REPO + frappe repo to use, default: https://github.com/frappe/frappe + -t FRAPPE_BRANCH, --frappe-branch FRAPPE_BRANCH + frappe repo to use, default: version-15 + -p PY_VERSION, --py-version PY_VERSION + python version, default: Not Set + -n NODE_VERSION, --node-version NODE_VERSION + node version, default: Not Set + -v, --verbose verbose output + -a ADMIN_PASSWORD, --admin-password ADMIN_PASSWORD + admin password for site, default: admin + -d DB_TYPE, --db-type DB_TYPE + Database type to use (e.g., mariadb or postgres) +``` + +A new bench and / or site is created for the client with following defaults. + +- MariaDB root password: `123` +- Admin password: `admin` + +> To use Postegres DB, comment the mariabdb service and uncomment postegres service. + +### Start Frappe with Visual Studio Code Python Debugging + +To enable Python debugging inside Visual Studio Code, you must first install the `ms-python.python` extension inside the container. This should have already happened automatically, but depending on your VSCode config, you can force it by: + +- Click on the extension icon inside VSCode +- Search `ms-python.python` +- Click on `Install on Dev Container: Frappe Bench` +- Click on 'Reload' + +We need to start bench separately through the VSCode debugger. For this reason, **instead** of running `bench start` you should run the following command inside the frappe-bench directory: + +```shell +honcho start \ + socketio \ + watch \ + schedule \ + worker_short \ + worker_long +``` + +Alternatively you can use the VSCode launch configuration "Honcho SocketIO Watch Schedule Worker" which launches the same command as above. + +This command starts all processes with the exception of Redis (which is already running in separate container) and the `web` process. The latter can can finally be started from the debugger tab of VSCode by clicking on the "play" button. + +You can now login with user `Administrator` and the password you choose when creating the site, if you followed this guide's unattended install that password is going to be `admin`. + +To debug workers, skip starting worker with honcho and start it with VSCode debugger. + +For advance vscode configuration in the devcontainer, change the config files in `development/.vscode`. + +## Developing using the interactive console + +You can launch a simple interactive shell console in the terminal with: + +```shell +bench --site development.localhost console +``` + +More likely, you may want to launch VSCode interactive console based on Jupyter kernel. + +Launch VSCode command palette (cmd+shift+p or ctrl+shift+p), run the command `Python: Select interpreter to start Jupyter server` and select `/workspace/development/frappe-bench/env/bin/python`. + +The first step is installing and updating the required software. Usually the frappe framework may require an older version of Jupyter, while VSCode likes to move fast, this can [cause issues](https://github.com/jupyter/jupyter_console/issues/158). For this reason we need to run the following command. + +```shell +/workspace/development/frappe-bench/env/bin/python -m pip install --upgrade jupyter ipykernel ipython +``` + +Then, run the command `Python: Show Python interactive window` from the VSCode command palette. + +Replace `development.localhost` with your site and run the following code in a Jupyter cell: + +```python +import frappe + +frappe.init(site='development.localhost', sites_path='/workspace/development/frappe-bench/sites') +frappe.connect() +frappe.local.lang = frappe.db.get_default('lang') +frappe.db.connect() +``` + +The first command can take a few seconds to be executed, this is to be expected. + +## Manually start containers + +In case you don't use VSCode, you may start the containers manually with the following command: + +### Running the containers + +```shell +docker-compose -f .devcontainer/docker-compose.yml up -d +``` + +And enter the interactive shell for the development container with the following command: + +```shell +docker exec -e "TERM=xterm-256color" -w /workspace/development -it devcontainer-frappe-1 bash +``` + +## Use additional services during development + +Add any service that is needed for development in the `.devcontainer/docker-compose.yml` then rebuild and reopen in devcontainer. + +e.g. + +```yaml +... +services: + ... + postgresql: + image: postgres:11.8 + environment: + POSTGRES_PASSWORD: 123 + volumes: + - postgresql-data:/var/lib/postgresql/data + ports: + - 5432:5432 + +volumes: + ... + postgresql-data: +``` + +Access the service by service name from the `frappe` development container. The above service will be accessible via hostname `postgresql`. If ports are published on to host, access it via `localhost:5432`. + +## Using Cypress UI tests + +To run cypress based UI tests in a docker environment, follow the below steps: + +1. Install and setup X11 tooling on VM using the script `install_x11_deps.sh` + +```shell + sudo bash ./install_x11_deps.sh +``` + +This script will install required deps, enable X11Forwarding and restart SSH daemon and export `DISPLAY` variable. + +2. Run X11 service `startx` or `xquartz` +3. Start docker compose services. +4. SSH into ui-tester service using `docker exec..` command +5. Export CYPRESS_baseUrl and other required env variables +6. Start Cypress UI console by issuing `cypress run command` + +> More references : [Cypress Official Documentation](https://www.cypress.io/blog/2019/05/02/run-cypress-with-a-single-docker-command) + +> Ensure DISPLAY environment is always exported. + +## Using Mailpit to test mail services + +To use Mailpit just uncomment the service in the docker-compose.yml file. +The Interface is then available under port 8025 and the smtp service can be used as mailpit:1025. diff --git a/docs/error-nginx-entrypoint-windows.md b/docs/error-nginx-entrypoint-windows.md index 7be1c2ca..011a82d5 100644 --- a/docs/error-nginx-entrypoint-windows.md +++ b/docs/error-nginx-entrypoint-windows.md @@ -1,12 +1,12 @@ -# Resolving Docker `nginx-entrypoint.sh` Script Not Found Error on Windows - -If you're encountering the error `exec /usr/local/bin/nginx-entrypoint.sh: no such file or directory` in a Docker container on Windows, follow these steps to resolve the issue. - -## 1. Check Line Endings - -On Windows, files often have `CRLF` line endings, while Linux systems expect `LF`. This can cause issues when executing shell scripts in Linux containers. - -- **Convert Line Endings using `dos2unix`:** - ```bash - dos2unix resources/nginx-entrypoint.sh - ``` +# Resolving Docker `nginx-entrypoint.sh` Script Not Found Error on Windows + +If you're encountering the error `exec /usr/local/bin/nginx-entrypoint.sh: no such file or directory` in a Docker container on Windows, follow these steps to resolve the issue. + +## 1. Check Line Endings + +On Windows, files often have `CRLF` line endings, while Linux systems expect `LF`. This can cause issues when executing shell scripts in Linux containers. + +- **Convert Line Endings using `dos2unix`:** + ```bash + dos2unix resources/nginx-entrypoint.sh + ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index ea615c37..a26d533a 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,963 +1,963 @@ -# Getting Started with Frappe Docker - -_A comprehensive guide for developers getting started with Frappe Docker, with comparisons to Django for teams familiar with that framework_ - -## Table of Contents - -- [How to Use This Guide](#how-to-use-this-guide) -- [Understanding Frappe Docker Architecture](#understanding-frappe-docker-architecture) -- [Repository Structure](#repository-structure) -- [Custom Apps Explained](#custom-apps-explained) -- [Development Workflow](#development-workflow) -- [Platform Notes](#platform-notes) -- [File Locations and Access](#file-locations-and-access) -- [Docker Concepts: Bind Mounts](#docker-concepts-bind-mounts) -- [Fork Management Best Practices](#fork-management-best-practices) -- [Quick Start Examples](#quick-start-examples) -- [Framework Comparisons](#framework-comparisons) - - [Frappe vs Django](#frappe-vs-django-concepts) -- [Resources and References](#resources-and-references) - ---- - -## How to Use This Guide - -Walk through the sections sequentially if you're onboarding from scratch, or jump directly using the Table of Contents. - -## Understanding Frappe Docker Architecture - -Frappe Docker provides a comprehensive containerized environment for developing and deploying Frappe/ERPNext applications. It uses a **multi-service architecture** that handles everything from web serving to background job processing. - -### Core Services - -The base compose file includes these essential services: - -- **configurator** - Initialization service that configures database and Redis connections; runs on startup and exits -- **backend** - Werkzeug development server for dynamic content processing -- **frontend** - Nginx reverse proxy that serves static assets and routes requests -- **websocket** - Node.js server running Socket.IO for real-time communications -- **queue-short/long** - Python workers using RQ (Redis Queue) for asynchronous background job processing -- **scheduler** - Python service that runs scheduled tasks using the schedule library - -Additional services are added through compose overrides: - -- **db** - MariaDB or PostgreSQL database server (via `compose.mariadb.yaml` or `compose.postgres.yaml`) -- **redis-cache/queue** - Redis instances for caching and job queues (via `compose.redis.yaml`) - -### How Services Work Together - -``` -User Request - ↓ -[frontend (Nginx)] → Static files served directly - ↓ -[backend (Werkzeug)] → Dynamic content processing - ↓ ↓ -[db (MariaDB)] [redis-cache] - -Background Tasks: -[scheduler] → [redis-queue] → [queue-short/long workers] - -Real-time: -[websocket (Socket.IO)] ←→ [redis-cache] -``` - -## Repository Structure - -### 📁 Core Configuration Files - -- **compose.yaml** - Main Docker Compose file defining all services -- **example.env** - Environment variables template (copy to `.env`) -- **pwd.yml** - "Play with Docker" - simplified single-file setup for quick testing -- **docker-bake.hcl** - Advanced Docker Buildx configuration for multi-architecture builds -- **docs/container-setup/env-variables.md** - Central reference for environment configuration logic and defaults - -### 📁 images/ - Docker Image Definitions - -Four predefined Dockerfiles are available, each serving different use cases: - -- **images/bench/** - Sets up only the Bench CLI for development or debugging; does not include runtime services -- **images/custom/** - Multi-purpose Python backend built from plain Python base image; installs apps from `apps.json`; suitable for **production** and testing; ideal when you need control over Python/Node versions -- **images/layered/** - Same final contents as `custom` but based on prebuilt images from Docker Hub; faster builds for production when using Frappe-managed dependency versions -- **images/production/** - Installs only Frappe and ERPNext (not customizable with `apps.json`); best for **quick starts or exploration**; for real deployments, use `custom` or `layered` - -> **Note:** For detailed build arguments and advanced configuration options, see [docs/container-setup/01-overview.md](container-setup/01-overview.md). - -### 📁 overrides/ - Compose File Extensions - -Docker Compose "overrides" that extend the base compose.yaml for different scenarios: - -- **compose.mariadb.yaml** - Adds MariaDB database service -- **compose.redis.yaml** - Adds Redis caching service -- **compose.proxy.yaml** - Adds Traefik reverse proxy for multi-site hosting -- **compose.https.yaml** - Adds SSL/TLS certificate management - -### 📁 development/ - Dev Environment - -- **development/installer.py** - Automated bench/site creation and configuration script -- Contains your local development files (git-ignored to prevent accidental commits) - -### 📁 resources/ - Runtime Templates - -- **nginx-entrypoint.sh** - Dynamic Nginx configuration generator script -- **nginx-template.conf** - Nginx configuration template with variable substitution - -## Custom Apps Explained - -### What Are Frappe Custom Apps? - -Custom apps are self-contained, modular business applications that extend Frappe's functionality. They follow a convention-over-configuration approach where the framework provides most boilerplate automatically. - -### Custom App Structure - -``` -my_custom_app/ -├── hooks.py # App configuration and hooks into Frappe lifecycle -├── modules.txt # List of business modules in this app -├── my_custom_app/ -│ ├── __init__.py -│ ├── config/ -│ │ └── desktop.py # Desktop workspace icons and shortcuts -│ ├── my_module/ # Business domain module (e.g., sales, inventory) -│ │ ├── doctype/ # Document Types (data models) -│ │ │ ├── customer/ -│ │ │ │ ├── customer.py # Python controller (business logic) -│ │ │ │ ├── customer.json # Model definition (schema, validation) -│ │ │ │ └── customer.js # Frontend logic (UI interactions) -│ │ └── page/ # Custom pages (dashboards, reports) -│ ├── public/ # Static assets (CSS, JS, images) -│ ├── templates/ # Jinja2 templates for web pages -│ └── www/ # Web pages accessible via routes -└── requirements.txt # Python package dependencies -``` - -### Built-in Features (Auto-generated) - -Every Frappe app automatically includes: - -- **REST API** - Automatic CRUD endpoints from DocType definitions -- **Permissions system** - Row-level and field-level access control -- **Audit trails** - Automatic version tracking and change history -- **Custom fields** - Runtime field additions without code changes -- **Workflows** - Configurable approval and state management -- **Reports** - Query builder and report designer -- **Print formats** - PDF generation with custom templates -- **Email integration** - Template-based email sending -- **File attachments** - Document attachment management - -### Creating Custom Apps - -```bash -# Enter the development container -docker exec -it bash - -# Create new app (interactive prompts will ask for details) -bench new-app my_custom_app - -# Install app to a site -bench --site mysite.com install-app my_custom_app - -# Create a new DocType (data model) -bench --site mysite.com console ->>> bench.new_doc("DocType", {...}) -# Or use the web UI: Setup → Customize → DocType → New -``` - -## Development Workflow - -### Quick Test Setup (pwd.yml) - -Perfect for evaluating Frappe Docker without any local setup: - -```bash -git clone https://github.com/frappe/frappe_docker -cd frappe_docker -docker compose -f pwd.yml up -d - -# Monitor site creation (takes ~5 minutes) -docker compose -f pwd.yml logs -f create-site - -# Access once "create-site" container exits successfully -# Visit http://localhost:8080 -# Login: Administrator / admin -``` - -### Full Development Setup - -For active development with hot-reload and debugging: - -1. **Copy devcontainer configuration:** - - ```bash - cp -R devcontainer-example .devcontainer - ``` - -2. **Open in VSCode with Dev Containers extension** (Remote - Containers) - - - VSCode will detect `.devcontainer` and prompt to reopen in container - -3. **Run automated installer:** - - ```bash - cd /workspace/development - python installer.py - # Follow interactive prompts for site name, apps to install, etc. - ``` - -4. **Access development files:** - ``` - development/frappe-bench/ # Your live development environment - ``` - -### Development File Locations - -``` -development/ -├── frappe-bench/ # Your actual Frappe installation -│ ├── apps/ # All installed Frappe applications -│ │ ├── frappe/ # Core framework (don't modify directly) -│ │ ├── erpnext/ # ERPNext application (if installed) -│ │ └── my_custom_app/ # Your custom apps (edit freely) -│ ├── sites/ # Multi-tenant sites -│ │ ├── development.localhost/ # Default dev site -│ │ │ ├── site_config.json # Site-specific config -│ │ │ └── private/files/ # Uploaded files -│ │ └── common_site_config.json # Shared configuration -│ ├── env/ # Python virtual environment -│ ├── logs/ # Application logs -│ └── config/ # Bench-level configuration -└── .vscode/ # VSCode workspace settings -``` - -### Common Development Commands - -```bash -# Inside container -bench start # Start development server with hot-reload - -# Database operations -bench migrate # Run database migrations -bench --site mysite.com migrate # Site-specific migration - -# Frontend builds -bench build # Build all app assets -bench build --app my_custom_app # Build specific app - -# Code generation -bench new-app # Create new app -bench new-site # Create new site - -# App management -bench get-app # Download app from git -bench install-app # Install app to current site -bench uninstall-app # Remove app from site - -# Debugging -bench console # Python REPL with Frappe context -bench mariadb # Database console -``` - -## Platform Notes - -### ARM64 and Apple Silicon - -- Enable Docker Desktop's Rosetta emulation for initial builds when running on Apple Silicon with x86-only images. -- Prefer published multi-arch images (`frappe/bench`, `frappe/erpnext`) or build locally with `docker buildx bake --set *.platform=linux/amd64,linux/arm64` to cover both architectures in one pass. -- When using `pwd.yml`, export `DOCKER_DEFAULT_PLATFORM=linux/arm64` (or select the provided compose profile) to avoid unexpected emulation. -- Keep bind mounts under your user home directory and apply `:cached` or `:delegated` consistency flags for better performance on macOS. - -## File Locations and Access - -### Accessing Container Files - -```bash -# Enter backend container shell -docker compose -f pwd.yml exec backend bash - -# Navigate to bench directory -cd /home/frappe/frappe-bench/ - -# Key directories: -/home/frappe/frappe-bench/apps/ # All Frappe apps -/home/frappe/frappe-bench/sites/ # Site data and configuration -/home/frappe/frappe-bench/logs/ # Application logs -/home/frappe/frappe-bench/env/ # Python virtual environment -``` - -### Copying Files from Containers - -```bash -# Copy entire app from container to host -docker compose -f pwd.yml cp backend:/home/frappe/frappe-bench/apps/my_app ./local-apps/ - -# Copy logs -docker compose -f pwd.yml cp backend:/home/frappe/frappe-bench/logs/ ./debug-logs/ - -# Copy site files -docker compose -f pwd.yml cp backend:/home/frappe/frappe-bench/sites/mysite.com ./backup/ -``` - -### Useful Container Commands - -```bash -# List all sites -docker compose -f pwd.yml exec backend bench list-sites - -# List installed apps for a site -docker compose -f pwd.yml exec backend bench --site mysite.com list-apps - -# View site configuration -docker compose -f pwd.yml exec backend cat /home/frappe/frappe-bench/sites/common_site_config.json - -# Check logs in real-time -docker compose -f pwd.yml logs -f backend - -# Execute bench command -docker compose -f pwd.yml exec backend bench --site mysite.com console - -# Backup site -docker compose -f pwd.yml exec backend bench --site mysite.com backup --with-files -``` - -## Docker Concepts: Bind Mounts - -### What Are Bind Mounts? - -Bind mounts create a direct connection between a directory on your host machine and a directory inside a container. Changes in either location are immediately reflected in the other - perfect for development where you want to edit code on your host and see changes in the container. - -### Bind Mount vs Named Volume vs Anonymous Volume - -| Type | Syntax | Use Case | Persistence | -| -------------------- | ------------------------------ | -------------------------- | ---------------------------- | -| **Bind Mount** | `./local/path:/container/path` | Development, config files | On host filesystem | -| **Named Volume** | `volume_name:/container/path` | Production data, databases | Docker-managed | -| **Anonymous Volume** | `/container/path` | Temporary/cache data | Docker-managed, auto-deleted | - -### Bind Mount Examples - -```yaml -services: - backend: - volumes: - # Development: Edit code on host, run in container - - ./my_custom_app:/home/frappe/frappe-bench/apps/my_custom_app - - # Configuration: Override container config with host file - - ./custom-config.json:/home/frappe/frappe-bench/sites/common_site_config.json:ro # :ro = read-only - - # Logs: Access container logs on host for debugging - - ./logs:/home/frappe/frappe-bench/logs - - # Database (not recommended for production) - - ./data/mysql:/var/lib/mysql - - # Named volume for production database - db: - volumes: - - db_data:/var/lib/mysql # Managed by Docker, survives container deletion - -volumes: - db_data: # Define named volume -``` - -### Performance Optimization (macOS/Windows) - -Docker on macOS/Windows uses a VM, making bind mounts slower. Use these flags: - -```yaml -volumes: - # :cached - Host writes are buffered (good for general development) - - ./development:/home/frappe/frappe-bench:cached - - # :delegated - Container writes are buffered (best when container writes heavily) - - ./development:/home/frappe/frappe-bench:delegated - - # :consistent - Full synchronization (slowest but safest) - - ./development:/home/frappe/frappe-bench:consistent -``` - -**Recommendation:** Use `:cached` for most development work on macOS/Windows. - -## Fork Management Best Practices - -### Initial Fork Setup - -```bash -# 1. Fork on GitHub (use the Fork button) - -# 2. Clone YOUR fork -git clone https://github.com/YOUR_USERNAME/frappe_docker -cd frappe_docker - -# 3. Add upstream remote (original repo) -git remote add upstream https://github.com/frappe/frappe_docker.git - -# 4. Verify remotes -git remote -v -# origin https://github.com/YOUR_USERNAME/frappe_docker (your fork) -# upstream https://github.com/frappe/frappe_docker (original) - -# 5. Create development branch -git checkout -b my-custom-setup -``` - -### Safe Customization Zones - -**✅ Safe (Won't conflict with upstream):** - -``` -development/ # Your entire dev environment - ├── frappe-bench/ # Local installation - └── .vscode/ # Your editor settings - -compose.my-*.yaml # Your custom compose overrides -scripts/my-*.sh # Your custom scripts -docs/my-*.md # Your custom documentation -.env.local # Local environment overrides -.gitignore.local # Additional gitignore rules -``` - -**⚠️ Modification Needed (May conflict):** - -``` -compose.yaml # Core - use overrides instead -docker-bake.hcl # Build config - use custom files -images/*/Dockerfile # Core images - extend rather than modify -``` - -**❌ Never Modify (Will break upstream sync):** - -``` -.github/workflows/ # CI/CD pipelines -images/*/ # Core image definitions -resources/ # Core templates -``` - -### Keeping Fork Updated - -```bash -# Regularly sync with upstream (weekly recommended) -git checkout main -git fetch upstream -git merge upstream/main -git push origin main - -# Update your development branch -git checkout my-custom-setup -git rebase main # Or: git merge main - -# If conflicts occur during rebase: -# 1. Fix conflicts in files -# 2. git add -# 3. git rebase --continue -# Or: git rebase --abort (to cancel) -``` - -### Custom Environment Pattern - -Create override files for your customizations: - -```yaml -# compose.my-env.yaml -version: "3.7" - -services: - backend: - environment: - # Your custom environment variables - - DEVELOPER_MODE=true - - MY_API_KEY=${MY_API_KEY} - volumes: - # Your custom bind mounts - - ./development/my-scripts:/home/frappe/my-scripts - - ./development/my-config:/home/frappe/config - - # Your additional services - my-monitoring: - image: prom/prometheus - ports: - - "9090:9090" - volumes: - - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml -# Use it: -# docker compose -f compose.yaml -f compose.my-env.yaml up -``` - -### .gitignore Strategy - -Add to `.gitignore` (or create `.gitignore.local`): - -```gitignore -# Local environment files -.env.local -*.local.yaml -compose.my-*.yaml - -# Development artifacts -development/frappe-bench/sites/* -development/frappe-bench/apps/* -!development/frappe-bench/apps.json -development/frappe-bench/logs/ -development/frappe-bench/env/ - -# Local customizations -my-local-configs/ -scripts/my-*.sh -docs/internal-*.md - -# IDE -.vscode/settings.json.local -.idea/ - -# Temporary files -*.swp -*.swo -*~ -.DS_Store -``` - -### Contributing Back to Upstream - -```bash -# 1. Create feature branch from main -git checkout main -git pull upstream main -git checkout -b feature/my-improvement - -# 2. Make changes and commit -git add . -git commit -m "feat: add awesome feature" - -# 3. Push to YOUR fork -git push origin feature/my-improvement - -# 4. Create Pull Request on GitHub -# Go to: https://github.com/frappe/frappe_docker -# Click "Compare & pull request" -``` - -## Quick Start Examples - -### 1. Quick Test (5 minutes) - -**Goal:** Try Frappe/ERPNext without any local setup - -```bash -# Clone and run -git clone https://github.com/frappe/frappe_docker -cd frappe_docker -docker compose -f pwd.yml up -d - -# Monitor setup progress (~5 minutes) -docker compose -f pwd.yml logs -f create-site - -# When complete, access: -# URL: http://localhost:8080 -# Username: Administrator -# Password: admin - -# Cleanup when done -docker compose -f pwd.yml down -v -``` - -### 2. Development Environment (15 minutes) - -**Goal:** Set up for daily development with hot-reload - -```bash -# Copy devcontainer config -cp -R devcontainer-example .devcontainer - -# Open in VSCode -# 1. Install "Dev Containers" extension -# 2. Command Palette (Ctrl+Shift+P) → "Reopen in Container" -# 3. Wait for container build (~5 min first time) - -# Inside container terminal: -cd /workspace/development -python installer.py - -# Follow prompts: -# - Site name: development.localhost -# - Install ERPNext: Yes -# - Version: version-15 - -# Start development server -cd frappe-bench -bench start - -# Access: http://localhost:8000 -# Edit files in: development/frappe-bench/apps/ -``` - -### 3. Custom App Development (30 minutes) - -**Goal:** Create and develop a custom Frappe application - -```bash -# Prerequisite: Complete Example 2 first - -# Inside development container -cd /workspace/development/frappe-bench - -# Create new app -bench new-app library_management -# Follow prompts (title, description, publisher, etc.) - -# Install to site -bench --site development.localhost install-app library_management - -# Create DocTypes via web UI: -# 1. Go to: http://localhost:8000 -# 2. Setup → Customize → DocType → New -# 3. Create: Book, Author, Borrower, etc. - -# Or create via code: -# Edit: apps/library_management/library_management/library_management/doctype/ - -# Build and reload -bench build --app library_management -# Server auto-reloads (bench start watches for changes) -``` - -### 4. Production Deployment (1 hour) - -**Goal:** Deploy Frappe in production with SSL - -```bash -# Follow detailed guide -# See: docs/single-server-example.md - -# Quick overview: -# 1. Setup server with Docker -# 2. Clone frappe_docker -# 3. Configure environment variables -# 4. Use compose.yaml + production overrides -# 5. Setup SSL with Traefik/Let's Encrypt -# 6. Deploy and monitor - -# Key files: -# - compose.yaml -# - compose.mariadb.yaml -# - compose.redis.yaml -# - compose.proxy.yaml -# - compose.https.yaml - -# Deploy command: -docker compose \ - -f compose.yaml \ - -f overrides/compose.mariadb.yaml \ - -f overrides/compose.redis.yaml \ - -f overrides/compose.https.yaml \ - up -d -``` - -### 5. Multi-Site Hosting - -**Goal:** Host multiple Frappe sites on one server - -```bash -# See: docs/port-based-multi-tenancy.md - -# Quick example: -# 1. Create multiple sites in development -bench new-site site1.com -bench new-site site2.com - -# 2. Configure Nginx/Traefik for routing -# 3. Each site gets own database -# 4. Shared Redis and application code -``` - ---- - -## Framework Comparisons - -> **Note:** This section provides comparisons to other frameworks for developers familiar with them. If you're new to all frameworks, you can skip this section - the rest of the guide is self-contained. - -### Frappe vs Django Concepts - -#### Project Structure Comparison - -**Django Project:** - -```python -myproject/ -├── myproject/ # Project settings -│ ├── settings.py -│ ├── urls.py -│ └── wsgi.py -├── blog/ # Django app -│ ├── models.py -│ ├── views.py -│ └── urls.py -├── shop/ # Django app -└── users/ # Django app -``` - -**Frappe Bench:** - -``` -bench/ -├── apps/ -│ ├── frappe/ # Core framework (comparable to Django itself) -│ ├── erpnext/ # Complete business app (like Django + DRF + Celery + admin) -│ ├── hrms/ # HR Management app -│ └── my_custom_app/ # YOUR custom app -└── sites/ - └── mysite.com/ # Site instance (like Django project + database) - ├── site_config.json - └── private/files/ -``` - -#### Conceptual Mapping - -| Django | Frappe | Notes | -| ------------------ | ----------------- | ----------------------------------------------- | -| Model | DocType | But includes UI, permissions, API automatically | -| View | Controller method | Much less code needed | -| Admin | Desk | More powerful, auto-generated | -| DRF Serializer | Built-in | Automatic from DocType | -| Celery task | Background job | Built-in, no separate setup | -| signals | hooks.py | More structured | -| Management command | bench command | More discoverable | - -#### Key Architectural Differences - -1. **Multi-tenancy** - - - Django: One app = one database (typically) - - Frappe: One installation = many sites, each with own database - -2. **Background Jobs** - - - Django: Requires Celery + Redis + worker setup - - Frappe: Built-in queue system, just use `enqueue()` - -3. **Real-time** - - - Django: Requires Channels + Redis + ASGI setup - - Frappe: Socket.IO built-in, automatic for DocType updates - -4. **Admin/Management** - - - Django: Admin for models, basic CRUD - - Frappe: Full-featured Desk with reports, dashboards, permissions - -5. **API** - - Django: Manual DRF setup, serializers, views - - Frappe: Automatic REST + RPC from DocType definitions - -#### Code Comparison Example - -**Creating a "Customer" model:** - -Django (requires ~50+ lines): - -```python -# models.py -class Customer(models.Model): - name = models.CharField(max_length=100) - email = models.EmailField(unique=True) - -# serializers.py -class CustomerSerializer(serializers.ModelSerializer): - # ... - -# views.py -class CustomerViewSet(viewsets.ModelViewSet): - # ... - -# urls.py -router.register(r'customers', CustomerViewSet) - -# admin.py -@admin.register(Customer) -class CustomerAdmin(admin.ModelAdmin): - # ... -``` - -Frappe (DocType JSON + ~10 lines Python): - -```json -// customer.json (auto-generated via UI or code) -{ - "name": "Customer", - "fields": [ - { "fieldname": "customer_name", "fieldtype": "Data" }, - { "fieldname": "email", "fieldtype": "Data", "unique": 1 } - ] -} -``` - -```python -# customer.py (only for custom business logic) -import frappe -from frappe.model.document import Document - -class Customer(Document): - def validate(self): - # Custom validation logic only - pass -``` - -✅ **Automatically includes:** - -- REST API (`/api/resource/Customer`) -- List view, Form view -- Search, Filters, Sorting -- Permissions (Create, Read, Update, Delete) -- Audit trail (created_by, modified_by, versions) -- Print formats, Email templates - -#### When to Choose Frappe vs Django - -**Choose Frappe when:** - -- Building business applications (ERP, CRM, project management) -- Need multi-tenancy out-of-the-box -- Want rapid development with auto-generated UI -- Need role-based permissions and workflows -- Building for non-technical users who need customization - -**Choose Django when:** - -- Building consumer web apps (social media, e-commerce frontend) -- Need full control over every aspect -- Have highly custom UI requirements -- Team is already Django-expert -- Building API-only services - -**Hybrid Approach:** -Many teams use both: Frappe for back-office/admin tools, Django for customer-facing web apps. - ---- - -## Resources and References - -### Official Documentation - -- [Frappe Framework Docs](https://frappeframework.com/docs) - Core framework documentation -- [Frappe Docker Docs](https://github.com/frappe/frappe_docker/tree/main/docs) - This repository's docs -- [ERPNext Documentation](https://docs.erpnext.com) - ERPNext user and developer docs -- [Docker Documentation](https://docs.docker.com) - Docker fundamentals - -### Key Files in This Repository - -- [`docs/development.md`](development.md) - Detailed development setup -- [`docs/container-setup/env-variables.md`](container-setup/env-variables.md) - Environment variable reference -- [`docs/single-server-example.md`](single-server-example.md) - Production deployment guide -- [`docs/site-operations.md`](site-operations.md) - Common site management tasks -- [`development/installer.py`](../development/installer.py) - Automated setup script -- [`pwd.yml`](../pwd.yml) - Quick test configuration -- [`compose.yaml`](../compose.yaml) - Base Docker Compose configuration - -### Community Resources - -- [Frappe Forum](https://discuss.frappe.io) - Community Q&A -- [Frappe School](https://frappe.school) - Video tutorials -- [Frappe GitHub](https://github.com/frappe/frappe) - Framework source code - -### Essential Docker Commands Reference - -```bash -# Service Management -docker compose up -d # Start all services in background -docker compose down # Stop and remove containers -docker compose down -v # Stop and remove volumes (data loss!) -docker compose restart # Restart specific service -docker compose ps # List running services -docker compose logs -f # Follow logs for service - -# Container Access -docker compose exec bash # Open shell in running container -docker compose exec # Run command in container -docker compose run # Run one-off command (creates new container) - -# Debugging -docker compose logs --tail=100 # Last 100 log lines -docker compose top # Show running processes -docker inspect # Detailed container info - -# Cleanup -docker system prune # Remove unused containers/networks -docker volume prune # Remove unused volumes (BE CAREFUL!) -docker image prune # Remove unused images -``` - -### Essential Bench Commands Reference - -```bash -# Site Operations -bench new-site # Create new site -bench drop-site # Delete site (asks confirmation) -bench list-sites # List all sites -bench use # Set default site - -# App Operations -bench get-app # Download app from git -bench get-app # Download from Frappe registry -bench install-app # Install to default site -bench install-app --site # Install to specific site -bench uninstall-app # Uninstall from default site -bench list-apps # List installed apps - -# Development -bench start # Start development server (hot-reload) -bench build # Build frontend assets -bench build --app # Build specific app -bench migrate # Run database migrations -bench clear-cache # Clear Redis cache -bench clear-website-cache # Clear website route cache - -# Database -bench mariadb # Open MariaDB console -bench backup # Backup default site -bench backup --with-files # Backup with uploaded files -bench restore # Restore backup - -# Code Generation -bench new-app # Create new app -bench --site console # Python REPL with Frappe context -bench --site execute "" # Execute Python code - -# Deployment -bench setup production # Setup for production (supervisor, nginx) -bench restart # Restart bench processes -bench update # Update framework and apps -``` - -### Troubleshooting Quick Reference - -| Issue | Solution | -| ------------------------- | ----------------------------------------------------- | -| Port 8080 already in use | Change `PWD_PORT` in `.env` or stop other service | -| Container won't start | Check logs: `docker compose logs ` | -| Site creation fails | Check `create-site` logs, ensure DB is ready | -| Can't connect to site | Wait 5 min for initialization, check container health | -| Permission errors | Check volume permissions, may need `chown` | -| Out of disk space | `docker system prune -a --volumes` (CAREFUL!) | -| Python packages missing | `bench pip install ` inside container | -| Frontend not building | `bench build --force`, check Node.js errors | -| Database connection fails | Check `common_site_config.json`, Redis/MariaDB status | - -### Getting Help - -1. **Check existing docs** - Most issues covered in [`docs/troubleshoot.md`](troubleshoot.md) -2. **Search Frappe Forum** - [discuss.frappe.io](https://discuss.frappe.io) -3. **GitHub Issues** - Search existing issues first -4. **Discord/Telegram** - Community real-time chat (links in main repo) - -### Contributing - -Found issues or improvements for this guide? - -- Create an issue: [frappe_docker/issues](https://github.com/frappe/frappe_docker/issues) -- Submit focused PRs: keep updates scoped and split large efforts across multiple pull requests. -- Review [CONTRIBUTING.md](../CONTRIBUTING.md) for coding standards and review expectations. - ---- - -_This guide provides a comprehensive overview of Frappe Docker for developers of all backgrounds. For specific use cases or advanced topics, refer to the linked documentation._ - -_Last updated: October 2025_ +# Getting Started with Frappe Docker + +_A comprehensive guide for developers getting started with Frappe Docker, with comparisons to Django for teams familiar with that framework_ + +## Table of Contents + +- [How to Use This Guide](#how-to-use-this-guide) +- [Understanding Frappe Docker Architecture](#understanding-frappe-docker-architecture) +- [Repository Structure](#repository-structure) +- [Custom Apps Explained](#custom-apps-explained) +- [Development Workflow](#development-workflow) +- [Platform Notes](#platform-notes) +- [File Locations and Access](#file-locations-and-access) +- [Docker Concepts: Bind Mounts](#docker-concepts-bind-mounts) +- [Fork Management Best Practices](#fork-management-best-practices) +- [Quick Start Examples](#quick-start-examples) +- [Framework Comparisons](#framework-comparisons) + - [Frappe vs Django](#frappe-vs-django-concepts) +- [Resources and References](#resources-and-references) + +--- + +## How to Use This Guide + +Walk through the sections sequentially if you're onboarding from scratch, or jump directly using the Table of Contents. + +## Understanding Frappe Docker Architecture + +Frappe Docker provides a comprehensive containerized environment for developing and deploying Frappe/ERPNext applications. It uses a **multi-service architecture** that handles everything from web serving to background job processing. + +### Core Services + +The base compose file includes these essential services: + +- **configurator** - Initialization service that configures database and Redis connections; runs on startup and exits +- **backend** - Werkzeug development server for dynamic content processing +- **frontend** - Nginx reverse proxy that serves static assets and routes requests +- **websocket** - Node.js server running Socket.IO for real-time communications +- **queue-short/long** - Python workers using RQ (Redis Queue) for asynchronous background job processing +- **scheduler** - Python service that runs scheduled tasks using the schedule library + +Additional services are added through compose overrides: + +- **db** - MariaDB or PostgreSQL database server (via `compose.mariadb.yaml` or `compose.postgres.yaml`) +- **redis-cache/queue** - Redis instances for caching and job queues (via `compose.redis.yaml`) + +### How Services Work Together + +``` +User Request + ↓ +[frontend (Nginx)] → Static files served directly + ↓ +[backend (Werkzeug)] → Dynamic content processing + ↓ ↓ +[db (MariaDB)] [redis-cache] + +Background Tasks: +[scheduler] → [redis-queue] → [queue-short/long workers] + +Real-time: +[websocket (Socket.IO)] ←→ [redis-cache] +``` + +## Repository Structure + +### 📁 Core Configuration Files + +- **compose.yaml** - Main Docker Compose file defining all services +- **example.env** - Environment variables template (copy to `.env`) +- **pwd.yml** - "Play with Docker" - simplified single-file setup for quick testing +- **docker-bake.hcl** - Advanced Docker Buildx configuration for multi-architecture builds +- **docs/container-setup/env-variables.md** - Central reference for environment configuration logic and defaults + +### 📁 images/ - Docker Image Definitions + +Four predefined Dockerfiles are available, each serving different use cases: + +- **images/bench/** - Sets up only the Bench CLI for development or debugging; does not include runtime services +- **images/custom/** - Multi-purpose Python backend built from plain Python base image; installs apps from `apps.json`; suitable for **production** and testing; ideal when you need control over Python/Node versions +- **images/layered/** - Same final contents as `custom` but based on prebuilt images from Docker Hub; faster builds for production when using Frappe-managed dependency versions +- **images/production/** - Installs only Frappe and ERPNext (not customizable with `apps.json`); best for **quick starts or exploration**; for real deployments, use `custom` or `layered` + +> **Note:** For detailed build arguments and advanced configuration options, see [docs/container-setup/01-overview.md](container-setup/01-overview.md). + +### 📁 overrides/ - Compose File Extensions + +Docker Compose "overrides" that extend the base compose.yaml for different scenarios: + +- **compose.mariadb.yaml** - Adds MariaDB database service +- **compose.redis.yaml** - Adds Redis caching service +- **compose.proxy.yaml** - Adds Traefik reverse proxy for multi-site hosting +- **compose.https.yaml** - Adds SSL/TLS certificate management + +### 📁 development/ - Dev Environment + +- **development/installer.py** - Automated bench/site creation and configuration script +- Contains your local development files (git-ignored to prevent accidental commits) + +### 📁 resources/ - Runtime Templates + +- **nginx-entrypoint.sh** - Dynamic Nginx configuration generator script +- **nginx-template.conf** - Nginx configuration template with variable substitution + +## Custom Apps Explained + +### What Are Frappe Custom Apps? + +Custom apps are self-contained, modular business applications that extend Frappe's functionality. They follow a convention-over-configuration approach where the framework provides most boilerplate automatically. + +### Custom App Structure + +``` +my_custom_app/ +├── hooks.py # App configuration and hooks into Frappe lifecycle +├── modules.txt # List of business modules in this app +├── my_custom_app/ +│ ├── __init__.py +│ ├── config/ +│ │ └── desktop.py # Desktop workspace icons and shortcuts +│ ├── my_module/ # Business domain module (e.g., sales, inventory) +│ │ ├── doctype/ # Document Types (data models) +│ │ │ ├── customer/ +│ │ │ │ ├── customer.py # Python controller (business logic) +│ │ │ │ ├── customer.json # Model definition (schema, validation) +│ │ │ │ └── customer.js # Frontend logic (UI interactions) +│ │ └── page/ # Custom pages (dashboards, reports) +│ ├── public/ # Static assets (CSS, JS, images) +│ ├── templates/ # Jinja2 templates for web pages +│ └── www/ # Web pages accessible via routes +└── requirements.txt # Python package dependencies +``` + +### Built-in Features (Auto-generated) + +Every Frappe app automatically includes: + +- **REST API** - Automatic CRUD endpoints from DocType definitions +- **Permissions system** - Row-level and field-level access control +- **Audit trails** - Automatic version tracking and change history +- **Custom fields** - Runtime field additions without code changes +- **Workflows** - Configurable approval and state management +- **Reports** - Query builder and report designer +- **Print formats** - PDF generation with custom templates +- **Email integration** - Template-based email sending +- **File attachments** - Document attachment management + +### Creating Custom Apps + +```bash +# Enter the development container +docker exec -it bash + +# Create new app (interactive prompts will ask for details) +bench new-app my_custom_app + +# Install app to a site +bench --site mysite.com install-app my_custom_app + +# Create a new DocType (data model) +bench --site mysite.com console +>>> bench.new_doc("DocType", {...}) +# Or use the web UI: Setup → Customize → DocType → New +``` + +## Development Workflow + +### Quick Test Setup (pwd.yml) + +Perfect for evaluating Frappe Docker without any local setup: + +```bash +git clone https://github.com/frappe/frappe_docker +cd frappe_docker +docker compose -f pwd.yml up -d + +# Monitor site creation (takes ~5 minutes) +docker compose -f pwd.yml logs -f create-site + +# Access once "create-site" container exits successfully +# Visit http://localhost:8080 +# Login: Administrator / admin +``` + +### Full Development Setup + +For active development with hot-reload and debugging: + +1. **Copy devcontainer configuration:** + + ```bash + cp -R devcontainer-example .devcontainer + ``` + +2. **Open in VSCode with Dev Containers extension** (Remote - Containers) + + - VSCode will detect `.devcontainer` and prompt to reopen in container + +3. **Run automated installer:** + + ```bash + cd /workspace/development + python installer.py + # Follow interactive prompts for site name, apps to install, etc. + ``` + +4. **Access development files:** + ``` + development/frappe-bench/ # Your live development environment + ``` + +### Development File Locations + +``` +development/ +├── frappe-bench/ # Your actual Frappe installation +│ ├── apps/ # All installed Frappe applications +│ │ ├── frappe/ # Core framework (don't modify directly) +│ │ ├── erpnext/ # ERPNext application (if installed) +│ │ └── my_custom_app/ # Your custom apps (edit freely) +│ ├── sites/ # Multi-tenant sites +│ │ ├── development.localhost/ # Default dev site +│ │ │ ├── site_config.json # Site-specific config +│ │ │ └── private/files/ # Uploaded files +│ │ └── common_site_config.json # Shared configuration +│ ├── env/ # Python virtual environment +│ ├── logs/ # Application logs +│ └── config/ # Bench-level configuration +└── .vscode/ # VSCode workspace settings +``` + +### Common Development Commands + +```bash +# Inside container +bench start # Start development server with hot-reload + +# Database operations +bench migrate # Run database migrations +bench --site mysite.com migrate # Site-specific migration + +# Frontend builds +bench build # Build all app assets +bench build --app my_custom_app # Build specific app + +# Code generation +bench new-app # Create new app +bench new-site # Create new site + +# App management +bench get-app # Download app from git +bench install-app # Install app to current site +bench uninstall-app # Remove app from site + +# Debugging +bench console # Python REPL with Frappe context +bench mariadb # Database console +``` + +## Platform Notes + +### ARM64 and Apple Silicon + +- Enable Docker Desktop's Rosetta emulation for initial builds when running on Apple Silicon with x86-only images. +- Prefer published multi-arch images (`frappe/bench`, `frappe/erpnext`) or build locally with `docker buildx bake --set *.platform=linux/amd64,linux/arm64` to cover both architectures in one pass. +- When using `pwd.yml`, export `DOCKER_DEFAULT_PLATFORM=linux/arm64` (or select the provided compose profile) to avoid unexpected emulation. +- Keep bind mounts under your user home directory and apply `:cached` or `:delegated` consistency flags for better performance on macOS. + +## File Locations and Access + +### Accessing Container Files + +```bash +# Enter backend container shell +docker compose -f pwd.yml exec backend bash + +# Navigate to bench directory +cd /home/frappe/frappe-bench/ + +# Key directories: +/home/frappe/frappe-bench/apps/ # All Frappe apps +/home/frappe/frappe-bench/sites/ # Site data and configuration +/home/frappe/frappe-bench/logs/ # Application logs +/home/frappe/frappe-bench/env/ # Python virtual environment +``` + +### Copying Files from Containers + +```bash +# Copy entire app from container to host +docker compose -f pwd.yml cp backend:/home/frappe/frappe-bench/apps/my_app ./local-apps/ + +# Copy logs +docker compose -f pwd.yml cp backend:/home/frappe/frappe-bench/logs/ ./debug-logs/ + +# Copy site files +docker compose -f pwd.yml cp backend:/home/frappe/frappe-bench/sites/mysite.com ./backup/ +``` + +### Useful Container Commands + +```bash +# List all sites +docker compose -f pwd.yml exec backend bench list-sites + +# List installed apps for a site +docker compose -f pwd.yml exec backend bench --site mysite.com list-apps + +# View site configuration +docker compose -f pwd.yml exec backend cat /home/frappe/frappe-bench/sites/common_site_config.json + +# Check logs in real-time +docker compose -f pwd.yml logs -f backend + +# Execute bench command +docker compose -f pwd.yml exec backend bench --site mysite.com console + +# Backup site +docker compose -f pwd.yml exec backend bench --site mysite.com backup --with-files +``` + +## Docker Concepts: Bind Mounts + +### What Are Bind Mounts? + +Bind mounts create a direct connection between a directory on your host machine and a directory inside a container. Changes in either location are immediately reflected in the other - perfect for development where you want to edit code on your host and see changes in the container. + +### Bind Mount vs Named Volume vs Anonymous Volume + +| Type | Syntax | Use Case | Persistence | +| -------------------- | ------------------------------ | -------------------------- | ---------------------------- | +| **Bind Mount** | `./local/path:/container/path` | Development, config files | On host filesystem | +| **Named Volume** | `volume_name:/container/path` | Production data, databases | Docker-managed | +| **Anonymous Volume** | `/container/path` | Temporary/cache data | Docker-managed, auto-deleted | + +### Bind Mount Examples + +```yaml +services: + backend: + volumes: + # Development: Edit code on host, run in container + - ./my_custom_app:/home/frappe/frappe-bench/apps/my_custom_app + + # Configuration: Override container config with host file + - ./custom-config.json:/home/frappe/frappe-bench/sites/common_site_config.json:ro # :ro = read-only + + # Logs: Access container logs on host for debugging + - ./logs:/home/frappe/frappe-bench/logs + + # Database (not recommended for production) + - ./data/mysql:/var/lib/mysql + + # Named volume for production database + db: + volumes: + - db_data:/var/lib/mysql # Managed by Docker, survives container deletion + +volumes: + db_data: # Define named volume +``` + +### Performance Optimization (macOS/Windows) + +Docker on macOS/Windows uses a VM, making bind mounts slower. Use these flags: + +```yaml +volumes: + # :cached - Host writes are buffered (good for general development) + - ./development:/home/frappe/frappe-bench:cached + + # :delegated - Container writes are buffered (best when container writes heavily) + - ./development:/home/frappe/frappe-bench:delegated + + # :consistent - Full synchronization (slowest but safest) + - ./development:/home/frappe/frappe-bench:consistent +``` + +**Recommendation:** Use `:cached` for most development work on macOS/Windows. + +## Fork Management Best Practices + +### Initial Fork Setup + +```bash +# 1. Fork on GitHub (use the Fork button) + +# 2. Clone YOUR fork +git clone https://github.com/YOUR_USERNAME/frappe_docker +cd frappe_docker + +# 3. Add upstream remote (original repo) +git remote add upstream https://github.com/frappe/frappe_docker.git + +# 4. Verify remotes +git remote -v +# origin https://github.com/YOUR_USERNAME/frappe_docker (your fork) +# upstream https://github.com/frappe/frappe_docker (original) + +# 5. Create development branch +git checkout -b my-custom-setup +``` + +### Safe Customization Zones + +**✅ Safe (Won't conflict with upstream):** + +``` +development/ # Your entire dev environment + ├── frappe-bench/ # Local installation + └── .vscode/ # Your editor settings + +compose.my-*.yaml # Your custom compose overrides +scripts/my-*.sh # Your custom scripts +docs/my-*.md # Your custom documentation +.env.local # Local environment overrides +.gitignore.local # Additional gitignore rules +``` + +**⚠️ Modification Needed (May conflict):** + +``` +compose.yaml # Core - use overrides instead +docker-bake.hcl # Build config - use custom files +images/*/Dockerfile # Core images - extend rather than modify +``` + +**❌ Never Modify (Will break upstream sync):** + +``` +.github/workflows/ # CI/CD pipelines +images/*/ # Core image definitions +resources/ # Core templates +``` + +### Keeping Fork Updated + +```bash +# Regularly sync with upstream (weekly recommended) +git checkout main +git fetch upstream +git merge upstream/main +git push origin main + +# Update your development branch +git checkout my-custom-setup +git rebase main # Or: git merge main + +# If conflicts occur during rebase: +# 1. Fix conflicts in files +# 2. git add +# 3. git rebase --continue +# Or: git rebase --abort (to cancel) +``` + +### Custom Environment Pattern + +Create override files for your customizations: + +```yaml +# compose.my-env.yaml +version: "3.7" + +services: + backend: + environment: + # Your custom environment variables + - DEVELOPER_MODE=true + - MY_API_KEY=${MY_API_KEY} + volumes: + # Your custom bind mounts + - ./development/my-scripts:/home/frappe/my-scripts + - ./development/my-config:/home/frappe/config + + # Your additional services + my-monitoring: + image: prom/prometheus + ports: + - "9090:9090" + volumes: + - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml +# Use it: +# docker compose -f compose.yaml -f compose.my-env.yaml up +``` + +### .gitignore Strategy + +Add to `.gitignore` (or create `.gitignore.local`): + +```gitignore +# Local environment files +.env.local +*.local.yaml +compose.my-*.yaml + +# Development artifacts +development/frappe-bench/sites/* +development/frappe-bench/apps/* +!development/frappe-bench/apps.json +development/frappe-bench/logs/ +development/frappe-bench/env/ + +# Local customizations +my-local-configs/ +scripts/my-*.sh +docs/internal-*.md + +# IDE +.vscode/settings.json.local +.idea/ + +# Temporary files +*.swp +*.swo +*~ +.DS_Store +``` + +### Contributing Back to Upstream + +```bash +# 1. Create feature branch from main +git checkout main +git pull upstream main +git checkout -b feature/my-improvement + +# 2. Make changes and commit +git add . +git commit -m "feat: add awesome feature" + +# 3. Push to YOUR fork +git push origin feature/my-improvement + +# 4. Create Pull Request on GitHub +# Go to: https://github.com/frappe/frappe_docker +# Click "Compare & pull request" +``` + +## Quick Start Examples + +### 1. Quick Test (5 minutes) + +**Goal:** Try Frappe/ERPNext without any local setup + +```bash +# Clone and run +git clone https://github.com/frappe/frappe_docker +cd frappe_docker +docker compose -f pwd.yml up -d + +# Monitor setup progress (~5 minutes) +docker compose -f pwd.yml logs -f create-site + +# When complete, access: +# URL: http://localhost:8080 +# Username: Administrator +# Password: admin + +# Cleanup when done +docker compose -f pwd.yml down -v +``` + +### 2. Development Environment (15 minutes) + +**Goal:** Set up for daily development with hot-reload + +```bash +# Copy devcontainer config +cp -R devcontainer-example .devcontainer + +# Open in VSCode +# 1. Install "Dev Containers" extension +# 2. Command Palette (Ctrl+Shift+P) → "Reopen in Container" +# 3. Wait for container build (~5 min first time) + +# Inside container terminal: +cd /workspace/development +python installer.py + +# Follow prompts: +# - Site name: development.localhost +# - Install ERPNext: Yes +# - Version: version-15 + +# Start development server +cd frappe-bench +bench start + +# Access: http://localhost:8000 +# Edit files in: development/frappe-bench/apps/ +``` + +### 3. Custom App Development (30 minutes) + +**Goal:** Create and develop a custom Frappe application + +```bash +# Prerequisite: Complete Example 2 first + +# Inside development container +cd /workspace/development/frappe-bench + +# Create new app +bench new-app library_management +# Follow prompts (title, description, publisher, etc.) + +# Install to site +bench --site development.localhost install-app library_management + +# Create DocTypes via web UI: +# 1. Go to: http://localhost:8000 +# 2. Setup → Customize → DocType → New +# 3. Create: Book, Author, Borrower, etc. + +# Or create via code: +# Edit: apps/library_management/library_management/library_management/doctype/ + +# Build and reload +bench build --app library_management +# Server auto-reloads (bench start watches for changes) +``` + +### 4. Production Deployment (1 hour) + +**Goal:** Deploy Frappe in production with SSL + +```bash +# Follow detailed guide +# See: docs/single-server-example.md + +# Quick overview: +# 1. Setup server with Docker +# 2. Clone frappe_docker +# 3. Configure environment variables +# 4. Use compose.yaml + production overrides +# 5. Setup SSL with Traefik/Let's Encrypt +# 6. Deploy and monitor + +# Key files: +# - compose.yaml +# - compose.mariadb.yaml +# - compose.redis.yaml +# - compose.proxy.yaml +# - compose.https.yaml + +# Deploy command: +docker compose \ + -f compose.yaml \ + -f overrides/compose.mariadb.yaml \ + -f overrides/compose.redis.yaml \ + -f overrides/compose.https.yaml \ + up -d +``` + +### 5. Multi-Site Hosting + +**Goal:** Host multiple Frappe sites on one server + +```bash +# See: docs/port-based-multi-tenancy.md + +# Quick example: +# 1. Create multiple sites in development +bench new-site site1.com +bench new-site site2.com + +# 2. Configure Nginx/Traefik for routing +# 3. Each site gets own database +# 4. Shared Redis and application code +``` + +--- + +## Framework Comparisons + +> **Note:** This section provides comparisons to other frameworks for developers familiar with them. If you're new to all frameworks, you can skip this section - the rest of the guide is self-contained. + +### Frappe vs Django Concepts + +#### Project Structure Comparison + +**Django Project:** + +```python +myproject/ +├── myproject/ # Project settings +│ ├── settings.py +│ ├── urls.py +│ └── wsgi.py +├── blog/ # Django app +│ ├── models.py +│ ├── views.py +│ └── urls.py +├── shop/ # Django app +└── users/ # Django app +``` + +**Frappe Bench:** + +``` +bench/ +├── apps/ +│ ├── frappe/ # Core framework (comparable to Django itself) +│ ├── erpnext/ # Complete business app (like Django + DRF + Celery + admin) +│ ├── hrms/ # HR Management app +│ └── my_custom_app/ # YOUR custom app +└── sites/ + └── mysite.com/ # Site instance (like Django project + database) + ├── site_config.json + └── private/files/ +``` + +#### Conceptual Mapping + +| Django | Frappe | Notes | +| ------------------ | ----------------- | ----------------------------------------------- | +| Model | DocType | But includes UI, permissions, API automatically | +| View | Controller method | Much less code needed | +| Admin | Desk | More powerful, auto-generated | +| DRF Serializer | Built-in | Automatic from DocType | +| Celery task | Background job | Built-in, no separate setup | +| signals | hooks.py | More structured | +| Management command | bench command | More discoverable | + +#### Key Architectural Differences + +1. **Multi-tenancy** + + - Django: One app = one database (typically) + - Frappe: One installation = many sites, each with own database + +2. **Background Jobs** + + - Django: Requires Celery + Redis + worker setup + - Frappe: Built-in queue system, just use `enqueue()` + +3. **Real-time** + + - Django: Requires Channels + Redis + ASGI setup + - Frappe: Socket.IO built-in, automatic for DocType updates + +4. **Admin/Management** + + - Django: Admin for models, basic CRUD + - Frappe: Full-featured Desk with reports, dashboards, permissions + +5. **API** + - Django: Manual DRF setup, serializers, views + - Frappe: Automatic REST + RPC from DocType definitions + +#### Code Comparison Example + +**Creating a "Customer" model:** + +Django (requires ~50+ lines): + +```python +# models.py +class Customer(models.Model): + name = models.CharField(max_length=100) + email = models.EmailField(unique=True) + +# serializers.py +class CustomerSerializer(serializers.ModelSerializer): + # ... + +# views.py +class CustomerViewSet(viewsets.ModelViewSet): + # ... + +# urls.py +router.register(r'customers', CustomerViewSet) + +# admin.py +@admin.register(Customer) +class CustomerAdmin(admin.ModelAdmin): + # ... +``` + +Frappe (DocType JSON + ~10 lines Python): + +```json +// customer.json (auto-generated via UI or code) +{ + "name": "Customer", + "fields": [ + { "fieldname": "customer_name", "fieldtype": "Data" }, + { "fieldname": "email", "fieldtype": "Data", "unique": 1 } + ] +} +``` + +```python +# customer.py (only for custom business logic) +import frappe +from frappe.model.document import Document + +class Customer(Document): + def validate(self): + # Custom validation logic only + pass +``` + +✅ **Automatically includes:** + +- REST API (`/api/resource/Customer`) +- List view, Form view +- Search, Filters, Sorting +- Permissions (Create, Read, Update, Delete) +- Audit trail (created_by, modified_by, versions) +- Print formats, Email templates + +#### When to Choose Frappe vs Django + +**Choose Frappe when:** + +- Building business applications (ERP, CRM, project management) +- Need multi-tenancy out-of-the-box +- Want rapid development with auto-generated UI +- Need role-based permissions and workflows +- Building for non-technical users who need customization + +**Choose Django when:** + +- Building consumer web apps (social media, e-commerce frontend) +- Need full control over every aspect +- Have highly custom UI requirements +- Team is already Django-expert +- Building API-only services + +**Hybrid Approach:** +Many teams use both: Frappe for back-office/admin tools, Django for customer-facing web apps. + +--- + +## Resources and References + +### Official Documentation + +- [Frappe Framework Docs](https://frappeframework.com/docs) - Core framework documentation +- [Frappe Docker Docs](https://github.com/frappe/frappe_docker/tree/main/docs) - This repository's docs +- [ERPNext Documentation](https://docs.erpnext.com) - ERPNext user and developer docs +- [Docker Documentation](https://docs.docker.com) - Docker fundamentals + +### Key Files in This Repository + +- [`docs/development.md`](development.md) - Detailed development setup +- [`docs/container-setup/env-variables.md`](container-setup/env-variables.md) - Environment variable reference +- [`docs/single-server-example.md`](single-server-example.md) - Production deployment guide +- [`docs/site-operations.md`](site-operations.md) - Common site management tasks +- [`development/installer.py`](../development/installer.py) - Automated setup script +- [`pwd.yml`](../pwd.yml) - Quick test configuration +- [`compose.yaml`](../compose.yaml) - Base Docker Compose configuration + +### Community Resources + +- [Frappe Forum](https://discuss.frappe.io) - Community Q&A +- [Frappe School](https://frappe.school) - Video tutorials +- [Frappe GitHub](https://github.com/frappe/frappe) - Framework source code + +### Essential Docker Commands Reference + +```bash +# Service Management +docker compose up -d # Start all services in background +docker compose down # Stop and remove containers +docker compose down -v # Stop and remove volumes (data loss!) +docker compose restart # Restart specific service +docker compose ps # List running services +docker compose logs -f # Follow logs for service + +# Container Access +docker compose exec bash # Open shell in running container +docker compose exec # Run command in container +docker compose run # Run one-off command (creates new container) + +# Debugging +docker compose logs --tail=100 # Last 100 log lines +docker compose top # Show running processes +docker inspect # Detailed container info + +# Cleanup +docker system prune # Remove unused containers/networks +docker volume prune # Remove unused volumes (BE CAREFUL!) +docker image prune # Remove unused images +``` + +### Essential Bench Commands Reference + +```bash +# Site Operations +bench new-site # Create new site +bench drop-site # Delete site (asks confirmation) +bench list-sites # List all sites +bench use # Set default site + +# App Operations +bench get-app # Download app from git +bench get-app # Download from Frappe registry +bench install-app # Install to default site +bench install-app --site # Install to specific site +bench uninstall-app # Uninstall from default site +bench list-apps # List installed apps + +# Development +bench start # Start development server (hot-reload) +bench build # Build frontend assets +bench build --app # Build specific app +bench migrate # Run database migrations +bench clear-cache # Clear Redis cache +bench clear-website-cache # Clear website route cache + +# Database +bench mariadb # Open MariaDB console +bench backup # Backup default site +bench backup --with-files # Backup with uploaded files +bench restore # Restore backup + +# Code Generation +bench new-app # Create new app +bench --site console # Python REPL with Frappe context +bench --site execute "" # Execute Python code + +# Deployment +bench setup production # Setup for production (supervisor, nginx) +bench restart # Restart bench processes +bench update # Update framework and apps +``` + +### Troubleshooting Quick Reference + +| Issue | Solution | +| ------------------------- | ----------------------------------------------------- | +| Port 8080 already in use | Change `PWD_PORT` in `.env` or stop other service | +| Container won't start | Check logs: `docker compose logs ` | +| Site creation fails | Check `create-site` logs, ensure DB is ready | +| Can't connect to site | Wait 5 min for initialization, check container health | +| Permission errors | Check volume permissions, may need `chown` | +| Out of disk space | `docker system prune -a --volumes` (CAREFUL!) | +| Python packages missing | `bench pip install ` inside container | +| Frontend not building | `bench build --force`, check Node.js errors | +| Database connection fails | Check `common_site_config.json`, Redis/MariaDB status | + +### Getting Help + +1. **Check existing docs** - Most issues covered in [`docs/troubleshoot.md`](troubleshoot.md) +2. **Search Frappe Forum** - [discuss.frappe.io](https://discuss.frappe.io) +3. **GitHub Issues** - Search existing issues first +4. **Discord/Telegram** - Community real-time chat (links in main repo) + +### Contributing + +Found issues or improvements for this guide? + +- Create an issue: [frappe_docker/issues](https://github.com/frappe/frappe_docker/issues) +- Submit focused PRs: keep updates scoped and split large efforts across multiple pull requests. +- Review [CONTRIBUTING.md](../CONTRIBUTING.md) for coding standards and review expectations. + +--- + +_This guide provides a comprehensive overview of Frappe Docker for developers of all backgrounds. For specific use cases or advanced topics, refer to the linked documentation._ + +_Last updated: October 2025_ diff --git a/docs/migrate-from-multi-image-setup.md b/docs/migrate-from-multi-image-setup.md index 0e3c640c..92599f5d 100644 --- a/docs/migrate-from-multi-image-setup.md +++ b/docs/migrate-from-multi-image-setup.md @@ -1,112 +1,112 @@ -## Migrate from multi-image setup - -All the containers now use same image. Use `frappe/erpnext` instead of `frappe/frappe-worker`, `frappe/frappe-nginx` , `frappe/frappe-socketio` , `frappe/erpnext-worker` and `frappe/erpnext-nginx`. - -Now you need to specify command and environment variables for following containers: - -### Frontend - -For `frontend` service to act as static assets frontend and reverse proxy, you need to pass `nginx-entrypoint.sh` as container `command` and `BACKEND` and `SOCKETIO` environment variables pointing `{host}:{port}` for gunicorn and websocket services. Check [environment variables](environment-variables.md) - -Now you only need to mount the `sites` volume at location `/home/frappe/frappe-bench/sites`. No need for `assets` volume and asset population script or steps. - -Example change: - -```yaml -# ... removed for brevity -frontend: - image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set} - command: - - nginx-entrypoint.sh - environment: - BACKEND: backend:8000 - SOCKETIO: websocket:9000 - volumes: - - sites:/home/frappe/frappe-bench/sites -# ... removed for brevity -``` - -### Websocket - -For `websocket` service to act as socketio backend, you need to pass `["node", "/home/frappe/frappe-bench/apps/frappe/socketio.js"]` as container `command` - -Example change: - -```yaml -# ... removed for brevity -websocket: - image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set} - command: - - node - - /home/frappe/frappe-bench/apps/frappe/socketio.js -# ... removed for brevity -``` - -### Configurator - -For `configurator` service to act as run once configuration job, you need to pass `["bash", "-c"]` as container `entrypoint` and bash script inline to yaml. There is no `configure.py` in the container now. - -Example change: - -```yaml -# ... removed for brevity -configurator: - image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set} - restart: "no" - entrypoint: - - bash - - -c - command: - - > - bench set-config -g db_host $$DB_HOST; - bench set-config -gp db_port $$DB_PORT; - bench set-config -g redis_cache "redis://$$REDIS_CACHE"; - bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; - bench set-config -gp socketio_port $$SOCKETIO_PORT; - environment: - DB_HOST: db - DB_PORT: "3306" - REDIS_CACHE: redis-cache:6379 - REDIS_QUEUE: redis-queue:6379 - SOCKETIO_PORT: "9000" -# ... removed for brevity -``` - -### Site Creation - -For `create-site` service to act as run once site creation job, you need to pass `["bash", "-c"]` as container `entrypoint` and bash script inline to yaml. Make sure to use `--mariadb-user-host-login-scope=%` as upstream bench is installed in container. - -The `WORKDIR` has changed to `/home/frappe/frappe-bench` like `bench` setup we are used to. So the path to find `common_site_config.json` has changed to `sites/common_site_config.json`. - -Example change: - -```yaml -# ... removed for brevity -create-site: - image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set} - restart: "no" - entrypoint: - - bash - - -c - command: - - > - wait-for-it -t 120 db:3306; - wait-for-it -t 120 redis-cache:6379; - wait-for-it -t 120 redis-queue:6379; - export start=`date +%s`; - until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ - [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ - [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; - do - echo "Waiting for sites/common_site_config.json to be created"; - sleep 5; - if (( `date +%s`-start > 120 )); then - echo "could not find sites/common_site_config.json with required keys"; - exit 1 - fi - done; - echo "sites/common_site_config.json found"; - bench new-site --mariadb-user-host-login-scope=% --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend; - -# ... removed for brevity -``` +## Migrate from multi-image setup + +All the containers now use same image. Use `frappe/erpnext` instead of `frappe/frappe-worker`, `frappe/frappe-nginx` , `frappe/frappe-socketio` , `frappe/erpnext-worker` and `frappe/erpnext-nginx`. + +Now you need to specify command and environment variables for following containers: + +### Frontend + +For `frontend` service to act as static assets frontend and reverse proxy, you need to pass `nginx-entrypoint.sh` as container `command` and `BACKEND` and `SOCKETIO` environment variables pointing `{host}:{port}` for gunicorn and websocket services. Check [environment variables](environment-variables.md) + +Now you only need to mount the `sites` volume at location `/home/frappe/frappe-bench/sites`. No need for `assets` volume and asset population script or steps. + +Example change: + +```yaml +# ... removed for brevity +frontend: + image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set} + command: + - nginx-entrypoint.sh + environment: + BACKEND: backend:8000 + SOCKETIO: websocket:9000 + volumes: + - sites:/home/frappe/frappe-bench/sites +# ... removed for brevity +``` + +### Websocket + +For `websocket` service to act as socketio backend, you need to pass `["node", "/home/frappe/frappe-bench/apps/frappe/socketio.js"]` as container `command` + +Example change: + +```yaml +# ... removed for brevity +websocket: + image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set} + command: + - node + - /home/frappe/frappe-bench/apps/frappe/socketio.js +# ... removed for brevity +``` + +### Configurator + +For `configurator` service to act as run once configuration job, you need to pass `["bash", "-c"]` as container `entrypoint` and bash script inline to yaml. There is no `configure.py` in the container now. + +Example change: + +```yaml +# ... removed for brevity +configurator: + image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set} + restart: "no" + entrypoint: + - bash + - -c + command: + - > + bench set-config -g db_host $$DB_HOST; + bench set-config -gp db_port $$DB_PORT; + bench set-config -g redis_cache "redis://$$REDIS_CACHE"; + bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; + bench set-config -gp socketio_port $$SOCKETIO_PORT; + environment: + DB_HOST: db + DB_PORT: "3306" + REDIS_CACHE: redis-cache:6379 + REDIS_QUEUE: redis-queue:6379 + SOCKETIO_PORT: "9000" +# ... removed for brevity +``` + +### Site Creation + +For `create-site` service to act as run once site creation job, you need to pass `["bash", "-c"]` as container `entrypoint` and bash script inline to yaml. Make sure to use `--mariadb-user-host-login-scope=%` as upstream bench is installed in container. + +The `WORKDIR` has changed to `/home/frappe/frappe-bench` like `bench` setup we are used to. So the path to find `common_site_config.json` has changed to `sites/common_site_config.json`. + +Example change: + +```yaml +# ... removed for brevity +create-site: + image: frappe/erpnext:${ERPNEXT_VERSION:?ERPNext version not set} + restart: "no" + entrypoint: + - bash + - -c + command: + - > + wait-for-it -t 120 db:3306; + wait-for-it -t 120 redis-cache:6379; + wait-for-it -t 120 redis-queue:6379; + export start=`date +%s`; + until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; + do + echo "Waiting for sites/common_site_config.json to be created"; + sleep 5; + if (( `date +%s`-start > 120 )); then + echo "could not find sites/common_site_config.json with required keys"; + exit 1 + fi + done; + echo "sites/common_site_config.json found"; + bench new-site --mariadb-user-host-login-scope=% --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend; + +# ... removed for brevity +``` diff --git a/docs/port-based-multi-tenancy.md b/docs/port-based-multi-tenancy.md index 2f469bfd..af5546c8 100644 --- a/docs/port-based-multi-tenancy.md +++ b/docs/port-based-multi-tenancy.md @@ -1,69 +1,69 @@ -WARNING: Do not use this in production if the site is going to be served over plain http. - -### Step 1 - -Remove the traefik service from docker-compose.yml - -### Step 2 - -Add service for each port that needs to be exposed. - -e.g. `port-site-1`, `port-site-2`, `port-site-3`. - -```yaml -# ... removed for brevity -services: - # ... removed for brevity - port-site-1: - image: frappe/erpnext:v14.11.1 - deploy: - restart_policy: - condition: on-failure - command: - - nginx-entrypoint.sh - environment: - BACKEND: backend:8000 - FRAPPE_SITE_NAME_HEADER: site1.local - SOCKETIO: websocket:9000 - volumes: - - sites:/home/frappe/frappe-bench/sites - ports: - - "8080:8080" - port-site-2: - image: frappe/erpnext:v14.11.1 - deploy: - restart_policy: - condition: on-failure - command: - - nginx-entrypoint.sh - environment: - BACKEND: backend:8000 - FRAPPE_SITE_NAME_HEADER: site2.local - SOCKETIO: websocket:9000 - volumes: - - sites:/home/frappe/frappe-bench/sites - ports: - - "8081:8080" - port-site-3: - image: frappe/erpnext:v14.11.1 - deploy: - restart_policy: - condition: on-failure - command: - - nginx-entrypoint.sh - environment: - BACKEND: backend:8000 - FRAPPE_SITE_NAME_HEADER: site3.local - SOCKETIO: websocket:9000 - volumes: - - sites:/home/frappe/frappe-bench/sites - ports: - - "8082:8080" -``` - -Notes: - -- Above setup will expose `site1.local`, `site2.local`, `site3.local` on port `8080`, `8081`, `8082` respectively. -- Change `site1.local` to site name to serve from bench. -- Change the `BACKEND` and `SOCKETIO` environment variables as per your service names. -- Make sure `sites:` volume is available as part of yaml. +WARNING: Do not use this in production if the site is going to be served over plain http. + +### Step 1 + +Remove the traefik service from docker-compose.yml + +### Step 2 + +Add service for each port that needs to be exposed. + +e.g. `port-site-1`, `port-site-2`, `port-site-3`. + +```yaml +# ... removed for brevity +services: + # ... removed for brevity + port-site-1: + image: frappe/erpnext:v14.11.1 + deploy: + restart_policy: + condition: on-failure + command: + - nginx-entrypoint.sh + environment: + BACKEND: backend:8000 + FRAPPE_SITE_NAME_HEADER: site1.local + SOCKETIO: websocket:9000 + volumes: + - sites:/home/frappe/frappe-bench/sites + ports: + - "8080:8080" + port-site-2: + image: frappe/erpnext:v14.11.1 + deploy: + restart_policy: + condition: on-failure + command: + - nginx-entrypoint.sh + environment: + BACKEND: backend:8000 + FRAPPE_SITE_NAME_HEADER: site2.local + SOCKETIO: websocket:9000 + volumes: + - sites:/home/frappe/frappe-bench/sites + ports: + - "8081:8080" + port-site-3: + image: frappe/erpnext:v14.11.1 + deploy: + restart_policy: + condition: on-failure + command: + - nginx-entrypoint.sh + environment: + BACKEND: backend:8000 + FRAPPE_SITE_NAME_HEADER: site3.local + SOCKETIO: websocket:9000 + volumes: + - sites:/home/frappe/frappe-bench/sites + ports: + - "8082:8080" +``` + +Notes: + +- Above setup will expose `site1.local`, `site2.local`, `site3.local` on port `8080`, `8081`, `8082` respectively. +- Change `site1.local` to site name to serve from bench. +- Change the `BACKEND` and `SOCKETIO` environment variables as per your service names. +- Make sure `sites:` volume is available as part of yaml. diff --git a/docs/setup-options.md b/docs/setup-options.md index 1d49ceea..1558e5b8 100644 --- a/docs/setup-options.md +++ b/docs/setup-options.md @@ -1,131 +1,131 @@ -# Containerized Production Setup - -Make sure you've cloned this repository and switch to the directory before executing following commands. - -Commands will generate YAML as per the environment for setup. - -## Prerequisites - -- [docker](https://docker.com/get-started) -- [docker compose v2](https://docs.docker.com/compose/cli-command) - -## Setup Environment Variables - -Copy the example docker environment file to `.env`: - -```sh -cp example.env .env -``` - -Note: To know more about environment variable [read here](./environment-variables.md). Set the necessary variables in the `.env` file. - -## Generate docker-compose.yml for variety of setups - -Notes: - -- Make sure to replace `` with the desired name you wish to set for the project. -- This setup is not to be used for development. A complete development environment is available [here](../development) - -### Store the yaml files - -YAML files generated by `docker compose config` command can be stored in a directory. We will create a directory called `gitops` in the user's home. - -```shell -mkdir ~/gitops -``` - -You can make the directory into a private git repo which stores the yaml and secrets. It can help in tracking changes. - -Instead of `docker compose config`, you can directly use `docker compose up` to start the containers and skip storing the yamls in `gitops` directory. - -### Setup Frappe without proxy and external MariaDB and Redis - -In this case make sure you've set `DB_HOST`, `DB_PORT`, `REDIS_CACHE` and `REDIS_QUEUE` environment variables or the `configurator` will fail. - -```sh -# Generate YAML -docker compose -f compose.yaml -f overrides/compose.noproxy.yaml config > ~/gitops/docker-compose.yml - -# Start containers -docker compose --project-name -f ~/gitops/docker-compose.yml up -d -``` - -### Setup ERPNext with proxy and external MariaDB and Redis - -In this case make sure you've set `DB_HOST`, `DB_PORT`, `REDIS_CACHE` and `REDIS_QUEUE` environment variables or the `configurator` will fail. - -```sh -# Generate YAML -docker compose -f compose.yaml \ - -f overrides/compose.proxy.yaml \ - config > ~/gitops/docker-compose.yml - -# Start containers -docker compose --project-name -f ~/gitops/docker-compose.yml up -d -``` - -### Setup Frappe using containerized MariaDB and Redis with Letsencrypt certificates. - -In this case make sure you've set `LETSENCRYPT_EMAIL` and `SITES` environment variables are set or certificates won't work. - -```sh -# Generate YAML -docker compose -f compose.yaml \ - -f overrides/compose.mariadb.yaml \ - -f overrides/compose.redis.yaml \ - -f overrides/compose.https.yaml \ - config > ~/gitops/docker-compose.yml - -# Start containers -docker compose --project-name -f ~/gitops/docker-compose.yml up -d -``` - -### Setup ERPNext using containerized MariaDB and Redis with Letsencrypt certificates. - -In this case make sure you've set `LETSENCRYPT_EMAIL` and `SITES` environment variables are set or certificates won't work. - -```sh -# Generate YAML -docker compose -f compose.yaml \ - -f overrides/compose.mariadb.yaml \ - -f overrides/compose.redis.yaml \ - -f overrides/compose.https.yaml \ - config > ~/gitops/docker-compose.yml - -# Start containers -docker compose --project-name -f ~/gitops/docker-compose.yml up -d -``` - -## Create first site - -After starting containers, the first site needs to be created. Refer [site operations](./site-operations.md#setup-new-site). - -## Updating Images - -Switch to the root of the `frappe_docker` directory before running the following commands: - -```sh -# Update environment variables ERPNEXT_VERSION and FRAPPE_VERSION -nano .env - -# Pull new images -docker compose -f compose.yaml \ - # ... your other overrides - config > ~/gitops/docker-compose.yml - -# Pull images -docker compose --project-name -f ~/gitops/docker-compose.yml pull - -# Stop containers -docker compose --project-name -f ~/gitops/docker-compose.yml down - -# Restart containers -docker compose --project-name -f ~/gitops/docker-compose.yml up -d -``` - -Note: - -- pull and stop container commands can be skipped if immutable image tags are used -- `docker compose up -d` will pull new immutable tags if not found. - -To migrate sites refer [site operations](./site-operations.md#migrate-site) +# Containerized Production Setup + +Make sure you've cloned this repository and switch to the directory before executing following commands. + +Commands will generate YAML as per the environment for setup. + +## Prerequisites + +- [docker](https://docker.com/get-started) +- [docker compose v2](https://docs.docker.com/compose/cli-command) + +## Setup Environment Variables + +Copy the example docker environment file to `.env`: + +```sh +cp example.env .env +``` + +Note: To know more about environment variable [read here](./environment-variables.md). Set the necessary variables in the `.env` file. + +## Generate docker-compose.yml for variety of setups + +Notes: + +- Make sure to replace `` with the desired name you wish to set for the project. +- This setup is not to be used for development. A complete development environment is available [here](../development) + +### Store the yaml files + +YAML files generated by `docker compose config` command can be stored in a directory. We will create a directory called `gitops` in the user's home. + +```shell +mkdir ~/gitops +``` + +You can make the directory into a private git repo which stores the yaml and secrets. It can help in tracking changes. + +Instead of `docker compose config`, you can directly use `docker compose up` to start the containers and skip storing the yamls in `gitops` directory. + +### Setup Frappe without proxy and external MariaDB and Redis + +In this case make sure you've set `DB_HOST`, `DB_PORT`, `REDIS_CACHE` and `REDIS_QUEUE` environment variables or the `configurator` will fail. + +```sh +# Generate YAML +docker compose -f compose.yaml -f overrides/compose.noproxy.yaml config > ~/gitops/docker-compose.yml + +# Start containers +docker compose --project-name -f ~/gitops/docker-compose.yml up -d +``` + +### Setup ERPNext with proxy and external MariaDB and Redis + +In this case make sure you've set `DB_HOST`, `DB_PORT`, `REDIS_CACHE` and `REDIS_QUEUE` environment variables or the `configurator` will fail. + +```sh +# Generate YAML +docker compose -f compose.yaml \ + -f overrides/compose.proxy.yaml \ + config > ~/gitops/docker-compose.yml + +# Start containers +docker compose --project-name -f ~/gitops/docker-compose.yml up -d +``` + +### Setup Frappe using containerized MariaDB and Redis with Letsencrypt certificates. + +In this case make sure you've set `LETSENCRYPT_EMAIL` and `SITES` environment variables are set or certificates won't work. + +```sh +# Generate YAML +docker compose -f compose.yaml \ + -f overrides/compose.mariadb.yaml \ + -f overrides/compose.redis.yaml \ + -f overrides/compose.https.yaml \ + config > ~/gitops/docker-compose.yml + +# Start containers +docker compose --project-name -f ~/gitops/docker-compose.yml up -d +``` + +### Setup ERPNext using containerized MariaDB and Redis with Letsencrypt certificates. + +In this case make sure you've set `LETSENCRYPT_EMAIL` and `SITES` environment variables are set or certificates won't work. + +```sh +# Generate YAML +docker compose -f compose.yaml \ + -f overrides/compose.mariadb.yaml \ + -f overrides/compose.redis.yaml \ + -f overrides/compose.https.yaml \ + config > ~/gitops/docker-compose.yml + +# Start containers +docker compose --project-name -f ~/gitops/docker-compose.yml up -d +``` + +## Create first site + +After starting containers, the first site needs to be created. Refer [site operations](./site-operations.md#setup-new-site). + +## Updating Images + +Switch to the root of the `frappe_docker` directory before running the following commands: + +```sh +# Update environment variables ERPNEXT_VERSION and FRAPPE_VERSION +nano .env + +# Pull new images +docker compose -f compose.yaml \ + # ... your other overrides + config > ~/gitops/docker-compose.yml + +# Pull images +docker compose --project-name -f ~/gitops/docker-compose.yml pull + +# Stop containers +docker compose --project-name -f ~/gitops/docker-compose.yml down + +# Restart containers +docker compose --project-name -f ~/gitops/docker-compose.yml up -d +``` + +Note: + +- pull and stop container commands can be skipped if immutable image tags are used +- `docker compose up -d` will pull new immutable tags if not found. + +To migrate sites refer [site operations](./site-operations.md#migrate-site) diff --git a/docs/setup_for_linux_mac.md b/docs/setup_for_linux_mac.md index a224e9fe..abb2539d 100644 --- a/docs/setup_for_linux_mac.md +++ b/docs/setup_for_linux_mac.md @@ -1,226 +1,226 @@ -# How to install ERPNext on linux/mac using Frappe_docker ? - -step1: clone the repo - -``` -git clone https://github.com/frappe/frappe_docker -``` - -step2: add platform: linux/amd64 to all services in the /pwd.yaml - -here is the update pwd.yml file - -```yml -version: "3" - -services: - backend: - image: frappe/erpnext:v15 - platform: linux/amd64 - deploy: - restart_policy: - condition: on-failure - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - configurator: - image: frappe/erpnext:v15 - platform: linux/amd64 - deploy: - restart_policy: - condition: none - entrypoint: - - bash - - -c - # add redis_socketio for backward compatibility - command: - - > - ls -1 apps > sites/apps.txt; - bench set-config -g db_host $$DB_HOST; - bench set-config -gp db_port $$DB_PORT; - bench set-config -g redis_cache "redis://$$REDIS_CACHE"; - bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; - bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; - bench set-config -gp socketio_port $$SOCKETIO_PORT; - environment: - DB_HOST: db - DB_PORT: "3306" - REDIS_CACHE: redis-cache:6379 - REDIS_QUEUE: redis-queue:6379 - SOCKETIO_PORT: "9000" - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - create-site: - image: frappe/erpnext:v15 - platform: linux/amd64 - deploy: - restart_policy: - condition: none - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - entrypoint: - - bash - - -c - command: - - > - wait-for-it -t 120 db:3306; - wait-for-it -t 120 redis-cache:6379; - wait-for-it -t 120 redis-queue:6379; - export start=`date +%s`; - until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ - [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ - [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; - do - echo "Waiting for sites/common_site_config.json to be created"; - sleep 5; - if (( `date +%s`-start > 120 )); then - echo "could not find sites/common_site_config.json with required keys"; - exit 1 - fi - done; - echo "sites/common_site_config.json found"; - bench new-site --mariadb-user-host-login-scope=% --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend; - - db: - image: mariadb:10.6 - platform: linux/amd64 - healthcheck: - test: mysqladmin ping -h localhost --password=admin - interval: 1s - retries: 20 - deploy: - restart_policy: - condition: on-failure - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 - environment: - MYSQL_ROOT_PASSWORD: admin - volumes: - - db-data:/var/lib/mysql - - frontend: - image: frappe/erpnext:v15 - platform: linux/amd64 - depends_on: - - websocket - deploy: - restart_policy: - condition: on-failure - command: - - nginx-entrypoint.sh - environment: - BACKEND: backend:8000 - FRAPPE_SITE_NAME_HEADER: frontend - SOCKETIO: websocket:9000 - UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1 - UPSTREAM_REAL_IP_HEADER: X-Forwarded-For - UPSTREAM_REAL_IP_RECURSIVE: "off" - PROXY_READ_TIMEOUT: 120 - CLIENT_MAX_BODY_SIZE: 50m - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - ports: - - "8080:8080" - - queue-long: - image: frappe/erpnext:v15 - platform: linux/amd64 - deploy: - restart_policy: - condition: on-failure - command: - - bench - - worker - - --queue - - long,default,short - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - queue-short: - image: frappe/erpnext:v15 - platform: linux/amd64 - deploy: - restart_policy: - condition: on-failure - command: - - bench - - worker - - --queue - - short,default - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - redis-queue: - image: redis:6.2-alpine - platform: linux/amd64 - deploy: - restart_policy: - condition: on-failure - volumes: - - redis-queue-data:/data - - redis-cache: - image: redis:6.2-alpine - platform: linux/amd64 - deploy: - restart_policy: - condition: on-failure - - scheduler: - image: frappe/erpnext:v15 - platform: linux/amd64 - deploy: - restart_policy: - condition: on-failure - command: - - bench - - schedule - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - websocket: - image: frappe/erpnext:v15 - platform: linux/amd64 - deploy: - restart_policy: - condition: on-failure - command: - - node - - /home/frappe/frappe-bench/apps/frappe/socketio.js - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - -volumes: - db-data: - redis-queue-data: - sites: - logs: -``` - -step3: run the docker - -``` -cd frappe_docker -``` - -``` -docker-compose -f ./pwd.yml up -``` - ---- - -Wait for couple of minutes. - -Open localhost:8080 +# How to install ERPNext on linux/mac using Frappe_docker ? + +step1: clone the repo + +``` +git clone https://github.com/frappe/frappe_docker +``` + +step2: add platform: linux/amd64 to all services in the /pwd.yaml + +here is the update pwd.yml file + +```yml +version: "3" + +services: + backend: + image: frappe/erpnext:v15 + platform: linux/amd64 + deploy: + restart_policy: + condition: on-failure + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + configurator: + image: frappe/erpnext:v15 + platform: linux/amd64 + deploy: + restart_policy: + condition: none + entrypoint: + - bash + - -c + # add redis_socketio for backward compatibility + command: + - > + ls -1 apps > sites/apps.txt; + bench set-config -g db_host $$DB_HOST; + bench set-config -gp db_port $$DB_PORT; + bench set-config -g redis_cache "redis://$$REDIS_CACHE"; + bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; + bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; + bench set-config -gp socketio_port $$SOCKETIO_PORT; + environment: + DB_HOST: db + DB_PORT: "3306" + REDIS_CACHE: redis-cache:6379 + REDIS_QUEUE: redis-queue:6379 + SOCKETIO_PORT: "9000" + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + create-site: + image: frappe/erpnext:v15 + platform: linux/amd64 + deploy: + restart_policy: + condition: none + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + entrypoint: + - bash + - -c + command: + - > + wait-for-it -t 120 db:3306; + wait-for-it -t 120 redis-cache:6379; + wait-for-it -t 120 redis-queue:6379; + export start=`date +%s`; + until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; + do + echo "Waiting for sites/common_site_config.json to be created"; + sleep 5; + if (( `date +%s`-start > 120 )); then + echo "could not find sites/common_site_config.json with required keys"; + exit 1 + fi + done; + echo "sites/common_site_config.json found"; + bench new-site --mariadb-user-host-login-scope=% --admin-password=admin --db-root-password=admin --install-app erpnext --set-default frontend; + + db: + image: mariadb:10.6 + platform: linux/amd64 + healthcheck: + test: mysqladmin ping -h localhost --password=admin + interval: 1s + retries: 20 + deploy: + restart_policy: + condition: on-failure + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 + environment: + MYSQL_ROOT_PASSWORD: admin + volumes: + - db-data:/var/lib/mysql + + frontend: + image: frappe/erpnext:v15 + platform: linux/amd64 + depends_on: + - websocket + deploy: + restart_policy: + condition: on-failure + command: + - nginx-entrypoint.sh + environment: + BACKEND: backend:8000 + FRAPPE_SITE_NAME_HEADER: frontend + SOCKETIO: websocket:9000 + UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1 + UPSTREAM_REAL_IP_HEADER: X-Forwarded-For + UPSTREAM_REAL_IP_RECURSIVE: "off" + PROXY_READ_TIMEOUT: 120 + CLIENT_MAX_BODY_SIZE: 50m + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + ports: + - "8080:8080" + + queue-long: + image: frappe/erpnext:v15 + platform: linux/amd64 + deploy: + restart_policy: + condition: on-failure + command: + - bench + - worker + - --queue + - long,default,short + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + queue-short: + image: frappe/erpnext:v15 + platform: linux/amd64 + deploy: + restart_policy: + condition: on-failure + command: + - bench + - worker + - --queue + - short,default + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + redis-queue: + image: redis:6.2-alpine + platform: linux/amd64 + deploy: + restart_policy: + condition: on-failure + volumes: + - redis-queue-data:/data + + redis-cache: + image: redis:6.2-alpine + platform: linux/amd64 + deploy: + restart_policy: + condition: on-failure + + scheduler: + image: frappe/erpnext:v15 + platform: linux/amd64 + deploy: + restart_policy: + condition: on-failure + command: + - bench + - schedule + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + websocket: + image: frappe/erpnext:v15 + platform: linux/amd64 + deploy: + restart_policy: + condition: on-failure + command: + - node + - /home/frappe/frappe-bench/apps/frappe/socketio.js + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + +volumes: + db-data: + redis-queue-data: + sites: + logs: +``` + +step3: run the docker + +``` +cd frappe_docker +``` + +``` +docker-compose -f ./pwd.yml up +``` + +--- + +Wait for couple of minutes. + +Open localhost:8080 diff --git a/docs/single-compose-setup.md b/docs/single-compose-setup.md index aea2914c..5f5573cd 100644 --- a/docs/single-compose-setup.md +++ b/docs/single-compose-setup.md @@ -1,38 +1,38 @@ -# Single Compose Setup - -This setup is a very simple single compose file that does everything to start required services and a frappe-bench. It is used to start play with docker instance with a site. The file is located in the root of repo and named `pwd.yml`. - -## Services - -### frappe-bench components - -- backend, serves gunicorn backend -- frontend, serves static assets through nginx frontend reverse proxies websocket and gunicorn. -- queue-long, long default and short rq worker. -- queue-short, default and short rq worker. -- schedule, event scheduler. -- websocket, socketio websocket for realtime communication. - -### Run once configuration - -- configurator, configures `common_site_config.json` to set db and redis hosts. -- create-site, creates one site to serve as default site for the frappe-bench. - -### Service dependencies - -- db, mariadb, container with frappe specific configuration. -- redis-cache, redis for cache data. -- redis-queue, redis for rq data and pub/sub. - -## Volumes - -- sites: Volume for bench data. Common config, all sites, all site configs and site files will be stored here. -- logs: Volume for bench logs. all process logs are dumped here. No need to mount it. Each container will create a temporary volume for logs if not specified. - -## Adaptation - -If you understand containers use the `pwd.yml` as a reference to build more complex setup like, single server example, Docker Swarm stack, Kubernetes Helm chart, etc. - -This serves only site called `frontend` through the nginx. `FRAPPE_SITE_NAME_HEADER` is set to `frontend` and a default site called `frontend` is created. - -Change the `$$host` will allow container to accept any host header and serve that site. To escape `$` in compose yaml use it like `$$`. To unset default site remove `currentsite.txt` file from `sites` directory. +# Single Compose Setup + +This setup is a very simple single compose file that does everything to start required services and a frappe-bench. It is used to start play with docker instance with a site. The file is located in the root of repo and named `pwd.yml`. + +## Services + +### frappe-bench components + +- backend, serves gunicorn backend +- frontend, serves static assets through nginx frontend reverse proxies websocket and gunicorn. +- queue-long, long default and short rq worker. +- queue-short, default and short rq worker. +- schedule, event scheduler. +- websocket, socketio websocket for realtime communication. + +### Run once configuration + +- configurator, configures `common_site_config.json` to set db and redis hosts. +- create-site, creates one site to serve as default site for the frappe-bench. + +### Service dependencies + +- db, mariadb, container with frappe specific configuration. +- redis-cache, redis for cache data. +- redis-queue, redis for rq data and pub/sub. + +## Volumes + +- sites: Volume for bench data. Common config, all sites, all site configs and site files will be stored here. +- logs: Volume for bench logs. all process logs are dumped here. No need to mount it. Each container will create a temporary volume for logs if not specified. + +## Adaptation + +If you understand containers use the `pwd.yml` as a reference to build more complex setup like, single server example, Docker Swarm stack, Kubernetes Helm chart, etc. + +This serves only site called `frontend` through the nginx. `FRAPPE_SITE_NAME_HEADER` is set to `frontend` and a default site called `frontend` is created. + +Change the `$$host` will allow container to accept any host header and serve that site. To escape `$` in compose yaml use it like `$$`. To unset default site remove `currentsite.txt` file from `sites` directory. diff --git a/docs/single-server-example.md b/docs/single-server-example.md index 0bb83798..3d7becfa 100644 --- a/docs/single-server-example.md +++ b/docs/single-server-example.md @@ -1,288 +1,288 @@ -### Single Server Example - -In this use case we have a single server with a static IP attached to it. It can be used in scenarios where one powerful VM has multiple benches and applications or one entry level VM with single site. For single bench, single site setup follow only up to the point where first bench and first site is added. If you choose this setup you can only scale vertically. If you need to scale horizontally you'll need to backup the sites and restore them on to cluster setup. - -We will setup the following: - -- Install docker and docker compose v2 on linux server. -- Install traefik service for internal load balancer and letsencrypt. -- Install MariaDB with containers. -- Setup project called `erpnext-one` and create sites `one.example.com` and `two.example.com` in the project. -- Setup project called `erpnext-two` and create sites `three.example.com` and `four.example.com` in the project. - -Explanation: - -Single instance of **Traefik** will be installed and act as internal loadbalancer for multiple benches and sites hosted on the server. It can also load balance other applications along with frappe benches, e.g. wordpress, metabase, etc. We only expose the ports `80` and `443` once with this instance of traefik. Traefik will also take care of letsencrypt automation for all sites installed on the server. _Why choose Traefik over Nginx Proxy Manager?_ Traefik doesn't need additional DB service and can store certificates in a json file in a volume. - -Single instance of **MariaDB** will be installed and act as database service for all the benches/projects installed on the server. - -Each instance of ERPNext project (bench) will have its own redis, socketio, gunicorn, nginx, workers and scheduler. It will connect to internal MariaDB by connecting to MariaDB network. It will expose sites to public through Traefik by connecting to Traefik network. - -### Install Docker - -Easiest way to install docker is to use the [convenience script](https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script). - -```shell -curl -fsSL https://get.docker.com | bash -``` - -Note: The documentation assumes Ubuntu LTS server is used. Use any distribution as long as the docker convenience script works. If the convenience script doesn't work, you'll need to install docker manually. - -### Install Compose V2 - -Refer [original documentation](https://docs.docker.com/compose/cli-command/#install-on-linux) for updated version. - -```shell -DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} -mkdir -p $DOCKER_CONFIG/cli-plugins -curl -SL https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose -chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose -``` - -### Prepare - -Clone `frappe_docker` repo for the needed YAMLs and change the current working directory of your shell to the cloned repo. - -```shell -git clone https://github.com/frappe/frappe_docker -cd frappe_docker -``` - -Create configuration and resources directory - -```shell -mkdir ~/gitops -``` - -The `~/gitops` directory will store all the resources that we use for setup. We will also keep the environment files in this directory as there will be multiple projects with different environment variables. You can create a private repo for this directory and track the changes there. - -### Install Traefik - -Basic Traefik setup using docker compose. - -Create a file called `traefik.env` in `~/gitops` - -```shell -echo 'TRAEFIK_DOMAIN=traefik.example.com' > ~/gitops/traefik.env -echo 'EMAIL=admin@example.com' >> ~/gitops/traefik.env -echo "HASHED_PASSWORD='$(openssl passwd -apr1 changeit)'" >> ~/gitops/traefik.env -``` - -Note: - -- Change the domain from `traefik.example.com` to the one used in production. DNS entry needs to point to the Server IP. -- Change the letsencrypt notification email from `admin@example.com` to correct email. -- Change the password from `changeit` to more secure. - -env file generated at location `~/gitops/traefik.env` will look like following: - -```env -TRAEFIK_DOMAIN=traefik.example.com -EMAIL=admin@example.com -HASHED_PASSWORD=$apr1$K.4gp7RT$tj9R2jHh0D4Gb5o5fIAzm/ -``` - -If Container does not deploy put the HASHED_PASSWORD in ''. - -Deploy the traefik container with letsencrypt SSL - -```shell -docker compose --project-name traefik \ - --env-file ~/gitops/traefik.env \ - -f overrides/compose.traefik.yaml \ - -f overrides/compose.traefik-ssl.yaml up -d -``` - -This will make the traefik dashboard available on `traefik.example.com` and all certificates will reside in the Docker volume `cert-data`. - -For LAN setup deploy the traefik container without overriding `overrides/compose.traefik-ssl.yaml`. - -### Install MariaDB - -Basic MariaDB setup using docker compose. - -Create a file called `mariadb.env` in `~/gitops` - -```shell -echo "DB_PASSWORD=changeit" > ~/gitops/mariadb.env -``` - -Note: - -- Change the password from `changeit` to more secure. - -env file generated at location `~/gitops/mariadb.env` will look like following: - -```env -DB_PASSWORD=changeit -``` - -Note: Change the password from `changeit` to more secure one. - -Deploy the mariadb container - -```shell -docker compose --project-name mariadb --env-file ~/gitops/mariadb.env -f overrides/compose.mariadb-shared.yaml up -d -``` - -This will make `mariadb-database` service available under `mariadb-network`. Data will reside in `/data/mariadb`. - -### Install ERPNext - -#### Create first bench - -Create first bench called `erpnext-one` with `one.example.com` and `two.example.com` - -Create a file called `erpnext-one.env` in `~/gitops` - -```shell -cp example.env ~/gitops/erpnext-one.env -sed -i 's/DB_PASSWORD=123/DB_PASSWORD=changeit/g' ~/gitops/erpnext-one.env -sed -i 's/DB_HOST=/DB_HOST=mariadb-database/g' ~/gitops/erpnext-one.env -sed -i 's/DB_PORT=/DB_PORT=3306/g' ~/gitops/erpnext-one.env -sed -i 's/SITES=`erp.example.com`/SITES=\`one.example.com\`,\`two.example.com\`/g' ~/gitops/erpnext-one.env -echo 'ROUTER=erpnext-one' >> ~/gitops/erpnext-one.env -echo "BENCH_NETWORK=erpnext-one" >> ~/gitops/erpnext-one.env -``` - -Note: - -- Change the password from `changeit` to the one set for MariaDB compose in the previous step. - -env file is generated at location `~/gitops/erpnext-one.env`. - -Create a yaml file called `erpnext-one.yaml` in `~/gitops` directory: - -```shell -docker compose --project-name erpnext-one \ - --env-file ~/gitops/erpnext-one.env \ - -f compose.yaml \ - -f overrides/compose.redis.yaml \ - -f overrides/compose.multi-bench.yaml \ - -f overrides/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-one.yaml -``` - -For LAN setup do not override `compose.multi-bench-ssl.yaml`. - -Use the above command after any changes are made to `erpnext-one.env` file to regenerate `~/gitops/erpnext-one.yaml`. e.g. after changing version to migrate the bench. - -Deploy `erpnext-one` containers: - -```shell -docker compose --project-name erpnext-one -f ~/gitops/erpnext-one.yaml up -d -``` - -Create sites `one.example.com` and `two.example.com`: - -```shell -# one.example.com -docker compose --project-name erpnext-one exec backend \ - bench new-site --mariadb-user-host-login-scope=% --db-root-password changeit --install-app erpnext --admin-password changeit one.example.com -``` - -You can stop here and have a single bench single site setup complete. Continue to add one more site to the current bench. - -```shell -# two.example.com -docker compose --project-name erpnext-one exec backend \ - bench new-site --mariadb-user-host-login-scope=% --db-root-password changeit --install-app erpnext --admin-password changeit two.example.com -``` - -#### Create second bench - -Setting up additional bench is optional. Continue only if you need multi bench setup. - -Create second bench called `erpnext-two` with `three.example.com` and `four.example.com` - -Create a file called `erpnext-two.env` in `~/gitops` - -```shell -curl -sL https://raw.githubusercontent.com/frappe/frappe_docker/main/example.env -o ~/gitops/erpnext-two.env -sed -i 's/DB_PASSWORD=123/DB_PASSWORD=changeit/g' ~/gitops/erpnext-two.env -sed -i 's/DB_HOST=/DB_HOST=mariadb-database/g' ~/gitops/erpnext-two.env -sed -i 's/DB_PORT=/DB_PORT=3306/g' ~/gitops/erpnext-two.env -echo "ROUTER=erpnext-two" >> ~/gitops/erpnext-two.env -echo "SITES=\`three.example.com\`,\`four.example.com\`" >> ~/gitops/erpnext-two.env -echo "BENCH_NETWORK=erpnext-two" >> ~/gitops/erpnext-two.env -``` - -Note: - -- Change the password from `changeit` to the one set for MariaDB compose in the previous step. - -env file is generated at location `~/gitops/erpnext-two.env`. - -Create a yaml file called `erpnext-two.yaml` in `~/gitops` directory: - -```shell -docker compose --project-name erpnext-two \ - --env-file ~/gitops/erpnext-two.env \ - -f compose.yaml \ - -f overrides/compose.redis.yaml \ - -f overrides/compose.multi-bench.yaml \ - -f overrides/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-two.yaml -``` - -Use the above command after any changes are made to `erpnext-two.env` file to regenerate `~/gitops/erpnext-two.yaml`. e.g. after changing version to migrate the bench. - -Deploy `erpnext-two` containers: - -```shell -docker compose --project-name erpnext-two -f ~/gitops/erpnext-two.yaml up -d -``` - -Create sites `three.example.com` and `four.example.com`: - -```shell -# three.example.com -docker compose --project-name erpnext-two exec backend \ - bench new-site --mariadb-user-host-login-scope=% --db-root-password changeit --install-app erpnext --admin-password changeit three.example.com -# four.example.com -docker compose --project-name erpnext-two exec backend \ - bench new-site --mariadb-user-host-login-scope=% --db-root-password changeit --install-app erpnext --admin-password changeit four.example.com -``` - -#### Create custom domain to existing site - -In case you need to point custom domain to existing site follow these steps. -Also useful if custom domain is required for LAN based access. - -Create environment file - -```shell -echo "ROUTER=custom-one-example" > ~/gitops/custom-one-example.env -echo "SITES=\`custom-one.example.com\`" >> ~/gitops/custom-one-example.env -echo "BASE_SITE=one.example.com" >> ~/gitops/custom-one-example.env -echo "BENCH_NETWORK=erpnext-one" >> ~/gitops/custom-one-example.env -``` - -Note: - -- Change the file name from `custom-one-example.env` to a logical one. -- Change `ROUTER` variable from `custom-one.example.com` to the one being added. -- Change `SITES` variable from `custom-one.example.com` to the one being added. You can add multiple sites quoted in backtick (`) and separated by commas. -- Change `BASE_SITE` variable from `one.example.com` to the one which is being pointed to. -- Change `BENCH_NETWORK` variable from `erpnext-one` to the one which was created with the bench. - -env file is generated at location mentioned in command. - -Generate yaml to reverse proxy: - -```shell -docker compose --project-name custom-one-example \ - --env-file ~/gitops/custom-one-example.env \ - -f overrides/compose.custom-domain.yaml \ - -f overrides/compose.custom-domain-ssl.yaml config > ~/gitops/custom-one-example.yaml -``` - -For LAN setup do not override `compose.custom-domain-ssl.yaml`. - -Deploy `erpnext-two` containers: - -```shell -docker compose --project-name custom-one-example -f ~/gitops/custom-one-example.yaml up -d -``` - -### Site operations - -Refer: [site operations](./site-operations.md) +### Single Server Example + +In this use case we have a single server with a static IP attached to it. It can be used in scenarios where one powerful VM has multiple benches and applications or one entry level VM with single site. For single bench, single site setup follow only up to the point where first bench and first site is added. If you choose this setup you can only scale vertically. If you need to scale horizontally you'll need to backup the sites and restore them on to cluster setup. + +We will setup the following: + +- Install docker and docker compose v2 on linux server. +- Install traefik service for internal load balancer and letsencrypt. +- Install MariaDB with containers. +- Setup project called `erpnext-one` and create sites `one.example.com` and `two.example.com` in the project. +- Setup project called `erpnext-two` and create sites `three.example.com` and `four.example.com` in the project. + +Explanation: + +Single instance of **Traefik** will be installed and act as internal loadbalancer for multiple benches and sites hosted on the server. It can also load balance other applications along with frappe benches, e.g. wordpress, metabase, etc. We only expose the ports `80` and `443` once with this instance of traefik. Traefik will also take care of letsencrypt automation for all sites installed on the server. _Why choose Traefik over Nginx Proxy Manager?_ Traefik doesn't need additional DB service and can store certificates in a json file in a volume. + +Single instance of **MariaDB** will be installed and act as database service for all the benches/projects installed on the server. + +Each instance of ERPNext project (bench) will have its own redis, socketio, gunicorn, nginx, workers and scheduler. It will connect to internal MariaDB by connecting to MariaDB network. It will expose sites to public through Traefik by connecting to Traefik network. + +### Install Docker + +Easiest way to install docker is to use the [convenience script](https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script). + +```shell +curl -fsSL https://get.docker.com | bash +``` + +Note: The documentation assumes Ubuntu LTS server is used. Use any distribution as long as the docker convenience script works. If the convenience script doesn't work, you'll need to install docker manually. + +### Install Compose V2 + +Refer [original documentation](https://docs.docker.com/compose/cli-command/#install-on-linux) for updated version. + +```shell +DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker} +mkdir -p $DOCKER_CONFIG/cli-plugins +curl -SL https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose +chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose +``` + +### Prepare + +Clone `frappe_docker` repo for the needed YAMLs and change the current working directory of your shell to the cloned repo. + +```shell +git clone https://github.com/frappe/frappe_docker +cd frappe_docker +``` + +Create configuration and resources directory + +```shell +mkdir ~/gitops +``` + +The `~/gitops` directory will store all the resources that we use for setup. We will also keep the environment files in this directory as there will be multiple projects with different environment variables. You can create a private repo for this directory and track the changes there. + +### Install Traefik + +Basic Traefik setup using docker compose. + +Create a file called `traefik.env` in `~/gitops` + +```shell +echo 'TRAEFIK_DOMAIN=traefik.example.com' > ~/gitops/traefik.env +echo 'EMAIL=admin@example.com' >> ~/gitops/traefik.env +echo "HASHED_PASSWORD='$(openssl passwd -apr1 changeit)'" >> ~/gitops/traefik.env +``` + +Note: + +- Change the domain from `traefik.example.com` to the one used in production. DNS entry needs to point to the Server IP. +- Change the letsencrypt notification email from `admin@example.com` to correct email. +- Change the password from `changeit` to more secure. + +env file generated at location `~/gitops/traefik.env` will look like following: + +```env +TRAEFIK_DOMAIN=traefik.example.com +EMAIL=admin@example.com +HASHED_PASSWORD=$apr1$K.4gp7RT$tj9R2jHh0D4Gb5o5fIAzm/ +``` + +If Container does not deploy put the HASHED_PASSWORD in ''. + +Deploy the traefik container with letsencrypt SSL + +```shell +docker compose --project-name traefik \ + --env-file ~/gitops/traefik.env \ + -f overrides/compose.traefik.yaml \ + -f overrides/compose.traefik-ssl.yaml up -d +``` + +This will make the traefik dashboard available on `traefik.example.com` and all certificates will reside in the Docker volume `cert-data`. + +For LAN setup deploy the traefik container without overriding `overrides/compose.traefik-ssl.yaml`. + +### Install MariaDB + +Basic MariaDB setup using docker compose. + +Create a file called `mariadb.env` in `~/gitops` + +```shell +echo "DB_PASSWORD=changeit" > ~/gitops/mariadb.env +``` + +Note: + +- Change the password from `changeit` to more secure. + +env file generated at location `~/gitops/mariadb.env` will look like following: + +```env +DB_PASSWORD=changeit +``` + +Note: Change the password from `changeit` to more secure one. + +Deploy the mariadb container + +```shell +docker compose --project-name mariadb --env-file ~/gitops/mariadb.env -f overrides/compose.mariadb-shared.yaml up -d +``` + +This will make `mariadb-database` service available under `mariadb-network`. Data will reside in `/data/mariadb`. + +### Install ERPNext + +#### Create first bench + +Create first bench called `erpnext-one` with `one.example.com` and `two.example.com` + +Create a file called `erpnext-one.env` in `~/gitops` + +```shell +cp example.env ~/gitops/erpnext-one.env +sed -i 's/DB_PASSWORD=123/DB_PASSWORD=changeit/g' ~/gitops/erpnext-one.env +sed -i 's/DB_HOST=/DB_HOST=mariadb-database/g' ~/gitops/erpnext-one.env +sed -i 's/DB_PORT=/DB_PORT=3306/g' ~/gitops/erpnext-one.env +sed -i 's/SITES=`erp.example.com`/SITES=\`one.example.com\`,\`two.example.com\`/g' ~/gitops/erpnext-one.env +echo 'ROUTER=erpnext-one' >> ~/gitops/erpnext-one.env +echo "BENCH_NETWORK=erpnext-one" >> ~/gitops/erpnext-one.env +``` + +Note: + +- Change the password from `changeit` to the one set for MariaDB compose in the previous step. + +env file is generated at location `~/gitops/erpnext-one.env`. + +Create a yaml file called `erpnext-one.yaml` in `~/gitops` directory: + +```shell +docker compose --project-name erpnext-one \ + --env-file ~/gitops/erpnext-one.env \ + -f compose.yaml \ + -f overrides/compose.redis.yaml \ + -f overrides/compose.multi-bench.yaml \ + -f overrides/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-one.yaml +``` + +For LAN setup do not override `compose.multi-bench-ssl.yaml`. + +Use the above command after any changes are made to `erpnext-one.env` file to regenerate `~/gitops/erpnext-one.yaml`. e.g. after changing version to migrate the bench. + +Deploy `erpnext-one` containers: + +```shell +docker compose --project-name erpnext-one -f ~/gitops/erpnext-one.yaml up -d +``` + +Create sites `one.example.com` and `two.example.com`: + +```shell +# one.example.com +docker compose --project-name erpnext-one exec backend \ + bench new-site --mariadb-user-host-login-scope=% --db-root-password changeit --install-app erpnext --admin-password changeit one.example.com +``` + +You can stop here and have a single bench single site setup complete. Continue to add one more site to the current bench. + +```shell +# two.example.com +docker compose --project-name erpnext-one exec backend \ + bench new-site --mariadb-user-host-login-scope=% --db-root-password changeit --install-app erpnext --admin-password changeit two.example.com +``` + +#### Create second bench + +Setting up additional bench is optional. Continue only if you need multi bench setup. + +Create second bench called `erpnext-two` with `three.example.com` and `four.example.com` + +Create a file called `erpnext-two.env` in `~/gitops` + +```shell +curl -sL https://raw.githubusercontent.com/frappe/frappe_docker/main/example.env -o ~/gitops/erpnext-two.env +sed -i 's/DB_PASSWORD=123/DB_PASSWORD=changeit/g' ~/gitops/erpnext-two.env +sed -i 's/DB_HOST=/DB_HOST=mariadb-database/g' ~/gitops/erpnext-two.env +sed -i 's/DB_PORT=/DB_PORT=3306/g' ~/gitops/erpnext-two.env +echo "ROUTER=erpnext-two" >> ~/gitops/erpnext-two.env +echo "SITES=\`three.example.com\`,\`four.example.com\`" >> ~/gitops/erpnext-two.env +echo "BENCH_NETWORK=erpnext-two" >> ~/gitops/erpnext-two.env +``` + +Note: + +- Change the password from `changeit` to the one set for MariaDB compose in the previous step. + +env file is generated at location `~/gitops/erpnext-two.env`. + +Create a yaml file called `erpnext-two.yaml` in `~/gitops` directory: + +```shell +docker compose --project-name erpnext-two \ + --env-file ~/gitops/erpnext-two.env \ + -f compose.yaml \ + -f overrides/compose.redis.yaml \ + -f overrides/compose.multi-bench.yaml \ + -f overrides/compose.multi-bench-ssl.yaml config > ~/gitops/erpnext-two.yaml +``` + +Use the above command after any changes are made to `erpnext-two.env` file to regenerate `~/gitops/erpnext-two.yaml`. e.g. after changing version to migrate the bench. + +Deploy `erpnext-two` containers: + +```shell +docker compose --project-name erpnext-two -f ~/gitops/erpnext-two.yaml up -d +``` + +Create sites `three.example.com` and `four.example.com`: + +```shell +# three.example.com +docker compose --project-name erpnext-two exec backend \ + bench new-site --mariadb-user-host-login-scope=% --db-root-password changeit --install-app erpnext --admin-password changeit three.example.com +# four.example.com +docker compose --project-name erpnext-two exec backend \ + bench new-site --mariadb-user-host-login-scope=% --db-root-password changeit --install-app erpnext --admin-password changeit four.example.com +``` + +#### Create custom domain to existing site + +In case you need to point custom domain to existing site follow these steps. +Also useful if custom domain is required for LAN based access. + +Create environment file + +```shell +echo "ROUTER=custom-one-example" > ~/gitops/custom-one-example.env +echo "SITES=\`custom-one.example.com\`" >> ~/gitops/custom-one-example.env +echo "BASE_SITE=one.example.com" >> ~/gitops/custom-one-example.env +echo "BENCH_NETWORK=erpnext-one" >> ~/gitops/custom-one-example.env +``` + +Note: + +- Change the file name from `custom-one-example.env` to a logical one. +- Change `ROUTER` variable from `custom-one.example.com` to the one being added. +- Change `SITES` variable from `custom-one.example.com` to the one being added. You can add multiple sites quoted in backtick (`) and separated by commas. +- Change `BASE_SITE` variable from `one.example.com` to the one which is being pointed to. +- Change `BENCH_NETWORK` variable from `erpnext-one` to the one which was created with the bench. + +env file is generated at location mentioned in command. + +Generate yaml to reverse proxy: + +```shell +docker compose --project-name custom-one-example \ + --env-file ~/gitops/custom-one-example.env \ + -f overrides/compose.custom-domain.yaml \ + -f overrides/compose.custom-domain-ssl.yaml config > ~/gitops/custom-one-example.yaml +``` + +For LAN setup do not override `compose.custom-domain-ssl.yaml`. + +Deploy `erpnext-two` containers: + +```shell +docker compose --project-name custom-one-example -f ~/gitops/custom-one-example.yaml up -d +``` + +### Site operations + +Refer: [site operations](./site-operations.md) diff --git a/docs/site-operations.md b/docs/site-operations.md index 550d9bdf..676c84b0 100644 --- a/docs/site-operations.md +++ b/docs/site-operations.md @@ -1,86 +1,86 @@ -# Site operations - -> 💡 You should setup `--project-name` option in `docker-compose` commands if you have non-standard project name. - -## Setup new site - -Note: - -- Wait for the `db` service to start and `configurator` to exit before trying to create a new site. Usually this takes up to 10 seconds. -- Also you have to pass `-p ` if `-p` passed previously eg. `docker-compose -p exec (rest of the command)`. - -```sh -docker-compose exec backend bench new-site --mariadb-user-host-login-scope=% --db-root-password --admin-password -``` - -If you need to install some app, specify `--install-app`. To see all options, just run `bench new-site --help`. - -To create Postgres site (assuming you already use [Postgres compose override](images-and-compose-files.md#overrides)) you need have to do set `root_login` and `root_password` in common config before that: - -```sh -docker-compose exec backend bench set-config -g root_login -docker-compose exec backend bench set-config -g root_password -``` - -Also command is slightly different: - -```sh -docker-compose exec backend bench new-site --mariadb-user-host-login-scope=% --db-type postgres --admin-password -``` - -## Push backup to S3 storage - -We have the script that helps to push latest backup to S3. - -```sh -docker-compose exec backend push_backup.py --site-name --bucket --region-name --endpoint-url --aws-access-key-id --aws-secret-access-key -``` - -Note that you can restore backup only manually. - -## Edit configs - -Editing config manually might be required in some cases, -one such case is to use Amazon RDS (or any other DBaaS). -For full instructions, refer to the [wiki](). Common question can be found in Issues and on forum. - -`common_site_config.json` or `site_config.json` from `sites` volume has to be edited using following command: - -```sh -docker run --rm -it \ - -v _sites:/sites \ - alpine vi /sites/common_site_config.json -``` - -Instead of `alpine` use any image of your choice. - -## Health check - -For socketio and gunicorn service ping the hostname:port and that will be sufficient. For workers and scheduler, there is a command that needs to be executed. - -```shell -docker-compose exec backend healthcheck.sh --ping-service mongodb:27017 -``` - -Additional services can be pinged as part of health check with option `-p` or `--ping-service`. - -This check ensures that given service should be connected along with services in common_site_config.json. -If connection to service(s) fails, the command fails with exit code 1. - ---- - -For reference of commands like `backup`, `drop-site` or `migrate` check [official guide](https://frappeframework.com/docs/v13/user/en/bench/frappe-commands) or run: - -```sh -docker-compose exec backend bench --help -``` - -## Migrate site - -Note: - -- Wait for the `db` service to start and `configurator` to exit before trying to migrate a site. Usually this takes up to 10 seconds. - -```sh -docker-compose exec backend bench --site migrate -``` +# Site operations + +> 💡 You should setup `--project-name` option in `docker-compose` commands if you have non-standard project name. + +## Setup new site + +Note: + +- Wait for the `db` service to start and `configurator` to exit before trying to create a new site. Usually this takes up to 10 seconds. +- Also you have to pass `-p ` if `-p` passed previously eg. `docker-compose -p exec (rest of the command)`. + +```sh +docker-compose exec backend bench new-site --mariadb-user-host-login-scope=% --db-root-password --admin-password +``` + +If you need to install some app, specify `--install-app`. To see all options, just run `bench new-site --help`. + +To create Postgres site (assuming you already use [Postgres compose override](images-and-compose-files.md#overrides)) you need have to do set `root_login` and `root_password` in common config before that: + +```sh +docker-compose exec backend bench set-config -g root_login +docker-compose exec backend bench set-config -g root_password +``` + +Also command is slightly different: + +```sh +docker-compose exec backend bench new-site --mariadb-user-host-login-scope=% --db-type postgres --admin-password +``` + +## Push backup to S3 storage + +We have the script that helps to push latest backup to S3. + +```sh +docker-compose exec backend push_backup.py --site-name --bucket --region-name --endpoint-url --aws-access-key-id --aws-secret-access-key +``` + +Note that you can restore backup only manually. + +## Edit configs + +Editing config manually might be required in some cases, +one such case is to use Amazon RDS (or any other DBaaS). +For full instructions, refer to the [wiki](). Common question can be found in Issues and on forum. + +`common_site_config.json` or `site_config.json` from `sites` volume has to be edited using following command: + +```sh +docker run --rm -it \ + -v _sites:/sites \ + alpine vi /sites/common_site_config.json +``` + +Instead of `alpine` use any image of your choice. + +## Health check + +For socketio and gunicorn service ping the hostname:port and that will be sufficient. For workers and scheduler, there is a command that needs to be executed. + +```shell +docker-compose exec backend healthcheck.sh --ping-service mongodb:27017 +``` + +Additional services can be pinged as part of health check with option `-p` or `--ping-service`. + +This check ensures that given service should be connected along with services in common_site_config.json. +If connection to service(s) fails, the command fails with exit code 1. + +--- + +For reference of commands like `backup`, `drop-site` or `migrate` check [official guide](https://frappeframework.com/docs/v13/user/en/bench/frappe-commands) or run: + +```sh +docker-compose exec backend bench --help +``` + +## Migrate site + +Note: + +- Wait for the `db` service to start and `configurator` to exit before trying to migrate a site. Usually this takes up to 10 seconds. + +```sh +docker-compose exec backend bench --site migrate +``` diff --git a/docs/tls-for-local-deployment.md b/docs/tls-for-local-deployment.md index 07a0787b..d1ed3a96 100644 --- a/docs/tls-for-local-deployment.md +++ b/docs/tls-for-local-deployment.md @@ -1,25 +1,25 @@ -# Accessing ERPNext through https on local deployment - -- ERPNext container deployment can be accessed through https easily using Caddy web server, Caddy will be used as reverse proxy and forward traffics to the frontend container. - -### Prerequisites - -- Caddy -- Adding a domain name to hosts file - -#### Installation of caddy webserver - -- Follow the official Caddy website for the installation guide https://caddyserver.com/docs/install - After completing the installation open the configuration file of Caddy ( You find the config file in ` /etc/caddy/Caddyfile`), add the following configuration to forward traffics to the ERPNext frontend container - -```js -erp.localdev.net { - tls internal - - reverse_proxy localhost:8085 { - - } -} -``` - -- Caddy's root certificate must be added to other computers if computers from different networks access the ERPNext through https. +# Accessing ERPNext through https on local deployment + +- ERPNext container deployment can be accessed through https easily using Caddy web server, Caddy will be used as reverse proxy and forward traffics to the frontend container. + +### Prerequisites + +- Caddy +- Adding a domain name to hosts file + +#### Installation of caddy webserver + +- Follow the official Caddy website for the installation guide https://caddyserver.com/docs/install + After completing the installation open the configuration file of Caddy ( You find the config file in ` /etc/caddy/Caddyfile`), add the following configuration to forward traffics to the ERPNext frontend container + +```js +erp.localdev.net { + tls internal + + reverse_proxy localhost:8085 { + + } +} +``` + +- Caddy's root certificate must be added to other computers if computers from different networks access the ERPNext through https. diff --git a/docs/troubleshoot.md b/docs/troubleshoot.md index 09d7f8b6..53c8c9e6 100644 --- a/docs/troubleshoot.md +++ b/docs/troubleshoot.md @@ -1,79 +1,79 @@ -1. [Fixing MariaDB issues after rebuilding the container](#fixing-mariadb-issues-after-rebuilding-the-container) -1. [docker-compose does not recognize variables from `.env` file](#docker-compose-does-not-recognize-variables-from-env-file) -1. [Windows Based Installation](#windows-based-installation) - -### Fixing MariaDB issues after rebuilding the container - -For any reason after rebuilding the container if you are not be able to access MariaDB correctly (i.e. `Access denied for user [...]`) with the previous configuration. Follow these instructions. - -First test for network issues. Manually connect to the database through the `backend` container: - -``` -docker exec -it frappe_docker-backend-1 bash -mysql -uroot -padmin -hdb -``` - -Replace `root` with the database root user name, `admin` with the root password, and `db` with the service name specified in the docker-compose `.yml` configuration file. If the connection to the database is successful, then the network configuration is correct and you can proceed to the next step. Otherwise, modify the docker-compose `.yml` configuration file, in the `configurator` service's `environment` section, to use the container names (`frappe_docker-db-1`, `frappe_docker-redis-cache-1`, `frappe_docker-redis-queue-1` or as otherwise shown with `docker ps`) instead of the service names and rebuild the containers. - -Then, the parameter `'db_name'@'%'` needs to be set in MariaDB and permission to the site database suitably assigned to the user. - -This step has to be repeated for all sites available under the current bench. -Example shows the queries to be executed for site `localhost` - -Open sites/localhost/site_config.json: - -```shell -code sites/localhost/site_config.json -``` - -and take note of the parameters `db_name` and `db_password`. - -Enter MariaDB Interactive shell: - -```shell -mysql -uroot -padmin -hdb -``` - -The parameter `'db_name'@'%'` must not be duplicated. Verify that it is unique with the command: - -``` -SELECT User, Host FROM mysql.user; -``` - -Delete duplicated entries, if found, with the following: - -``` -DROP USER 'db_name'@'host'; -``` - -Modify permissions by executing following queries replacing `db_name` and `db_password` with the values found in site_config.json. - -```sql --- if there is no user created already first try to created it using the next command --- CREATE USER 'db_name'@'%' IDENTIFIED BY 'your_password'; --- skip the upgrade command below if you use the create command above -UPDATE mysql.global_priv SET Host = '%' where User = 'db_name'; FLUSH PRIVILEGES; -SET PASSWORD FOR 'db_name'@'%' = PASSWORD('db_password'); FLUSH PRIVILEGES; -GRANT ALL PRIVILEGES ON `db_name`.* TO 'db_name'@'%' IDENTIFIED BY 'db_password' WITH GRANT OPTION; FLUSH PRIVILEGES; -EXIT; -``` - -Note: For MariaDB 10.3 and older use `mysql.user` instead of `mysql.global_priv`. - -### docker-compose does not recognize variables from `.env` file - -If you are using old version of `docker-compose` the .env file needs to be located in directory from where the docker-compose command is executed. There may also be difference in official `docker-compose` and the one packaged by distro. Use `--env-file=.env` if available to explicitly specify the path to file. - -### Windows Based Installation - -- Set environment variable `COMPOSE_CONVERT_WINDOWS_PATHS` e.g. `set COMPOSE_CONVERT_WINDOWS_PATHS=1` -- While using docker machine, port-forward the ports of VM to ports of host machine. (ports 8080/8000/9000) -- Name all the sites ending with `.localhost`. and access it via browser locally. e.g. `http://site1.localhost` - -### Redo installation - -- If you have made changes and just want to start over again (abandoning all changes), remove all docker - - containers - - images - - volumes -- Install a fresh +1. [Fixing MariaDB issues after rebuilding the container](#fixing-mariadb-issues-after-rebuilding-the-container) +1. [docker-compose does not recognize variables from `.env` file](#docker-compose-does-not-recognize-variables-from-env-file) +1. [Windows Based Installation](#windows-based-installation) + +### Fixing MariaDB issues after rebuilding the container + +For any reason after rebuilding the container if you are not be able to access MariaDB correctly (i.e. `Access denied for user [...]`) with the previous configuration. Follow these instructions. + +First test for network issues. Manually connect to the database through the `backend` container: + +``` +docker exec -it frappe_docker-backend-1 bash +mysql -uroot -padmin -hdb +``` + +Replace `root` with the database root user name, `admin` with the root password, and `db` with the service name specified in the docker-compose `.yml` configuration file. If the connection to the database is successful, then the network configuration is correct and you can proceed to the next step. Otherwise, modify the docker-compose `.yml` configuration file, in the `configurator` service's `environment` section, to use the container names (`frappe_docker-db-1`, `frappe_docker-redis-cache-1`, `frappe_docker-redis-queue-1` or as otherwise shown with `docker ps`) instead of the service names and rebuild the containers. + +Then, the parameter `'db_name'@'%'` needs to be set in MariaDB and permission to the site database suitably assigned to the user. + +This step has to be repeated for all sites available under the current bench. +Example shows the queries to be executed for site `localhost` + +Open sites/localhost/site_config.json: + +```shell +code sites/localhost/site_config.json +``` + +and take note of the parameters `db_name` and `db_password`. + +Enter MariaDB Interactive shell: + +```shell +mysql -uroot -padmin -hdb +``` + +The parameter `'db_name'@'%'` must not be duplicated. Verify that it is unique with the command: + +``` +SELECT User, Host FROM mysql.user; +``` + +Delete duplicated entries, if found, with the following: + +``` +DROP USER 'db_name'@'host'; +``` + +Modify permissions by executing following queries replacing `db_name` and `db_password` with the values found in site_config.json. + +```sql +-- if there is no user created already first try to created it using the next command +-- CREATE USER 'db_name'@'%' IDENTIFIED BY 'your_password'; +-- skip the upgrade command below if you use the create command above +UPDATE mysql.global_priv SET Host = '%' where User = 'db_name'; FLUSH PRIVILEGES; +SET PASSWORD FOR 'db_name'@'%' = PASSWORD('db_password'); FLUSH PRIVILEGES; +GRANT ALL PRIVILEGES ON `db_name`.* TO 'db_name'@'%' IDENTIFIED BY 'db_password' WITH GRANT OPTION; FLUSH PRIVILEGES; +EXIT; +``` + +Note: For MariaDB 10.3 and older use `mysql.user` instead of `mysql.global_priv`. + +### docker-compose does not recognize variables from `.env` file + +If you are using old version of `docker-compose` the .env file needs to be located in directory from where the docker-compose command is executed. There may also be difference in official `docker-compose` and the one packaged by distro. Use `--env-file=.env` if available to explicitly specify the path to file. + +### Windows Based Installation + +- Set environment variable `COMPOSE_CONVERT_WINDOWS_PATHS` e.g. `set COMPOSE_CONVERT_WINDOWS_PATHS=1` +- While using docker machine, port-forward the ports of VM to ports of host machine. (ports 8080/8000/9000) +- Name all the sites ending with `.localhost`. and access it via browser locally. e.g. `http://site1.localhost` + +### Redo installation + +- If you have made changes and just want to start over again (abandoning all changes), remove all docker + - containers + - images + - volumes +- Install a fresh diff --git a/example.env b/example.env index 74174834..5a512a21 100644 --- a/example.env +++ b/example.env @@ -1,55 +1,55 @@ -# Reference: https://github.com/frappe/frappe_docker/blob/main/docs/environment-variables.md - -ERPNEXT_VERSION=v15.88.1 - -DB_PASSWORD=123 - -#Only if you use docker secrets for the db password -DB_PASSWORD_SECRETS_FILE= - -# Only if you use external database -DB_HOST= -DB_PORT= - -# Only if you use external Redis -REDIS_CACHE= -REDIS_QUEUE= - -# Only with HTTPS override -LETSENCRYPT_EMAIL=mail@example.com - -# These environment variables are not required. - -# Default value is `$$host` which resolves site by host. For example, if your host is `example.com`, -# site's name should be `example.com`, or if host is `127.0.0.1` (local debugging), it should be `127.0.0.1`. -# This variable allows to override described behavior. Let's say you create site named `mysite` -# and do want to access it by `127.0.0.1` host. Than you would set this variable to `mysite`. -FRAPPE_SITE_NAME_HEADER= - -# Default value is `8080`. -HTTP_PUBLISH_PORT= - -# Default value is `127.0.0.1`. Set IP address as our trusted upstream address. -UPSTREAM_REAL_IP_ADDRESS= - -# Default value is `X-Forwarded-For`. Set request header field whose value will be used to replace the client address -UPSTREAM_REAL_IP_HEADER= - -# Allowed values are on|off. Default value is `off`. If recursive search is disabled, -# the original client address that matches one of the trusted addresses -# is replaced by the last address sent in the request header field defined by the real_ip_header directive. -# If recursive search is enabled, the original client address that matches one of the trusted addresses is replaced by the last non-trusted address sent in the request header field. -UPSTREAM_REAL_IP_RECURSIVE= - -# All Values Allowed by nginx proxy_read_timeout are allowed, default value is 120s -# Useful if you have longrunning print formats or slow loading sites -PROXY_READ_TIMEOUT= - -# All Values allowed by nginx client_max_body_size are allowed, default value is 50m -# Necessary if the upload limit in the frappe application is increased -CLIENT_MAX_BODY_SIZE= - -# List of sites for letsencrypt certificates quoted with backtick (`) and separated by comma (,) -# More https://doc.traefik.io/traefik/routing/routers/#rule -# About acme https://doc.traefik.io/traefik/https/acme/#domain-definition -SITES=`erp.example.com` +# Reference: https://github.com/frappe/frappe_docker/blob/main/docs/environment-variables.md + +ERPNEXT_VERSION=v15.88.1 + +DB_PASSWORD=123 + +#Only if you use docker secrets for the db password +DB_PASSWORD_SECRETS_FILE= + +# Only if you use external database +DB_HOST= +DB_PORT= + +# Only if you use external Redis +REDIS_CACHE= +REDIS_QUEUE= + +# Only with HTTPS override +LETSENCRYPT_EMAIL=mail@example.com + +# These environment variables are not required. + +# Default value is `$$host` which resolves site by host. For example, if your host is `example.com`, +# site's name should be `example.com`, or if host is `127.0.0.1` (local debugging), it should be `127.0.0.1`. +# This variable allows to override described behavior. Let's say you create site named `mysite` +# and do want to access it by `127.0.0.1` host. Than you would set this variable to `mysite`. +FRAPPE_SITE_NAME_HEADER= + +# Default value is `8080`. +HTTP_PUBLISH_PORT= + +# Default value is `127.0.0.1`. Set IP address as our trusted upstream address. +UPSTREAM_REAL_IP_ADDRESS= + +# Default value is `X-Forwarded-For`. Set request header field whose value will be used to replace the client address +UPSTREAM_REAL_IP_HEADER= + +# Allowed values are on|off. Default value is `off`. If recursive search is disabled, +# the original client address that matches one of the trusted addresses +# is replaced by the last address sent in the request header field defined by the real_ip_header directive. +# If recursive search is enabled, the original client address that matches one of the trusted addresses is replaced by the last non-trusted address sent in the request header field. +UPSTREAM_REAL_IP_RECURSIVE= + +# All Values Allowed by nginx proxy_read_timeout are allowed, default value is 120s +# Useful if you have longrunning print formats or slow loading sites +PROXY_READ_TIMEOUT= + +# All Values allowed by nginx client_max_body_size are allowed, default value is 50m +# Necessary if the upload limit in the frappe application is increased +CLIENT_MAX_BODY_SIZE= + +# List of sites for letsencrypt certificates quoted with backtick (`) and separated by comma (,) +# More https://doc.traefik.io/traefik/routing/routers/#rule +# About acme https://doc.traefik.io/traefik/https/acme/#domain-definition +SITES=`erp.example.com` diff --git a/images/bench/Dockerfile b/images/bench/Dockerfile index 181a6623..9a66151c 100644 --- a/images/bench/Dockerfile +++ b/images/bench/Dockerfile @@ -1,164 +1,164 @@ -FROM debian:bookworm-slim AS bench - -LABEL author=frappé - -ARG GIT_REPO=https://github.com/frappe/bench.git -ARG GIT_BRANCH=v5.x - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ - # For frappe framework - git \ - mariadb-client \ - postgresql-client \ - gettext-base \ - wget \ - # for PDF - libssl-dev \ - fonts-cantarell \ - xfonts-75dpi \ - xfonts-base \ - # weasyprint dependencies - libpango-1.0-0 \ - libharfbuzz0b \ - libpangoft2-1.0-0 \ - libpangocairo-1.0-0 \ - # to work inside the container - locales \ - build-essential \ - cron \ - curl \ - vim \ - sudo \ - iputils-ping \ - watch \ - tree \ - nano \ - less \ - software-properties-common \ - bash-completion \ - # For psycopg2 - libpq-dev \ - # Other - libffi-dev \ - liblcms2-dev \ - libldap2-dev \ - libmariadb-dev \ - libsasl2-dev \ - libtiff5-dev \ - libwebp-dev \ - pkg-config \ - redis-tools \ - rlwrap \ - tk8.6-dev \ - ssh-client \ - # VSCode container requirements - net-tools \ - # For pyenv build dependencies - # https://github.com/frappe/frappe_docker/issues/840#issuecomment-1185206895 - make \ - # For pandas - libbz2-dev \ - # For bench execute - libsqlite3-dev \ - # For other dependencies - zlib1g-dev \ - libreadline-dev \ - llvm \ - libncurses5-dev \ - libncursesw5-dev \ - xz-utils \ - tk-dev \ - liblzma-dev \ - file \ - # For MIME type detection - media-types \ - && rm -rf /var/lib/apt/lists/* - -RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \ - && dpkg-reconfigure --frontend=noninteractive locales - -# Detect arch and install wkhtmltopdf -ARG WKHTMLTOPDF_VERSION=0.12.6.1-3 -ARG WKHTMLTOPDF_DISTRO=bookworm -RUN if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \ - && if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \ - && downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \ - && wget -q https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \ - && dpkg -i $downloaded_file \ - && rm $downloaded_file - -# Create new user with home directory, improve docker compatibility with UID/GID 1000, -# add user to sudo group, allow passwordless sudo, switch to that user -# and change directory to user home directory -RUN groupadd -g 1000 frappe \ - && useradd --no-log-init -r -m -u 1000 -g 1000 -G sudo frappe \ - && echo "frappe ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers - -USER frappe -WORKDIR /home/frappe - -# Install Python via pyenv -ENV PYTHON_VERSION_V14=3.10.13 -ENV PYTHON_VERSION=3.11.6 -ENV PYENV_ROOT=/home/frappe/.pyenv -ENV PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH - -# From https://github.com/pyenv/pyenv#basic-github-checkout -RUN git clone --depth 1 https://github.com/pyenv/pyenv.git .pyenv \ - && pyenv install $PYTHON_VERSION_V14 \ - && pyenv install $PYTHON_VERSION \ - && PYENV_VERSION=$PYTHON_VERSION_V14 pip install --no-cache-dir virtualenv \ - && PYENV_VERSION=$PYTHON_VERSION pip install --no-cache-dir virtualenv \ - && pyenv global $PYTHON_VERSION $PYTHON_VERSION_v14 \ - && sed -Ei -e '/^([^#]|$)/ {a export PYENV_ROOT="/home/frappe/.pyenv" a export PATH="$PYENV_ROOT/bin:$PATH" a ' -e ':a' -e '$!{n;ba};}' ~/.profile \ - && echo 'eval "$(pyenv init --path)"' >>~/.profile \ - && echo 'eval "$(pyenv init -)"' >>~/.bashrc - -# Clone and install bench in the local user home directory -# For development, bench source is located in ~/.bench -ENV PATH=/home/frappe/.local/bin:$PATH -# Skip editable-bench warning -# https://github.com/frappe/bench/commit/20560c97c4246b2480d7358c722bc9ad13606138 -RUN git clone ${GIT_REPO} --depth 1 -b ${GIT_BRANCH} .bench \ - && pip install --no-cache-dir --user -e .bench \ - && echo "export PATH=/home/frappe/.local/bin:\$PATH" >>/home/frappe/.bashrc \ - && echo "export BENCH_DEVELOPER=1" >>/home/frappe/.bashrc - -# Install Node via nvm -ENV NODE_VERSION_14=16.20.2 -ENV NODE_VERSION=20.19.2 -ENV NVM_DIR=/home/frappe/.nvm -ENV PATH=${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH} - -RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \ - && . ${NVM_DIR}/nvm.sh \ - && nvm install ${NODE_VERSION_14} \ - && nvm use v${NODE_VERSION_14} \ - && npm install -g yarn \ - && nvm install ${NODE_VERSION} \ - && nvm use v${NODE_VERSION} \ - && npm install -g yarn \ - && nvm alias default v${NODE_VERSION} \ - && rm -rf ${NVM_DIR}/.cache \ - && echo 'export NVM_DIR="/home/frappe/.nvm"' >>~/.bashrc \ - && echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> ~/.bashrc \ - && echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >> ~/.bashrc - - -EXPOSE 8000-8005 9000-9005 6787 - -FROM bench AS bench-test - -# Print version and verify bashrc is properly sourced so that everything works -# in the interactive shell and Dockerfile - -RUN node --version \ - && npm --version \ - && yarn --version \ - && bench --help - -RUN bash -c "node --version" \ - && bash -c "npm --version" \ - && bash -c "yarn --version" \ - && bash -c "bench --help" +FROM debian:bookworm-slim AS bench + +LABEL author=frappé + +ARG GIT_REPO=https://github.com/frappe/bench.git +ARG GIT_BRANCH=v5.x + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + # For frappe framework + git \ + mariadb-client \ + postgresql-client \ + gettext-base \ + wget \ + # for PDF + libssl-dev \ + fonts-cantarell \ + xfonts-75dpi \ + xfonts-base \ + # weasyprint dependencies + libpango-1.0-0 \ + libharfbuzz0b \ + libpangoft2-1.0-0 \ + libpangocairo-1.0-0 \ + # to work inside the container + locales \ + build-essential \ + cron \ + curl \ + vim \ + sudo \ + iputils-ping \ + watch \ + tree \ + nano \ + less \ + software-properties-common \ + bash-completion \ + # For psycopg2 + libpq-dev \ + # Other + libffi-dev \ + liblcms2-dev \ + libldap2-dev \ + libmariadb-dev \ + libsasl2-dev \ + libtiff5-dev \ + libwebp-dev \ + pkg-config \ + redis-tools \ + rlwrap \ + tk8.6-dev \ + ssh-client \ + # VSCode container requirements + net-tools \ + # For pyenv build dependencies + # https://github.com/frappe/frappe_docker/issues/840#issuecomment-1185206895 + make \ + # For pandas + libbz2-dev \ + # For bench execute + libsqlite3-dev \ + # For other dependencies + zlib1g-dev \ + libreadline-dev \ + llvm \ + libncurses5-dev \ + libncursesw5-dev \ + xz-utils \ + tk-dev \ + liblzma-dev \ + file \ + # For MIME type detection + media-types \ + && rm -rf /var/lib/apt/lists/* + +RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \ + && dpkg-reconfigure --frontend=noninteractive locales + +# Detect arch and install wkhtmltopdf +ARG WKHTMLTOPDF_VERSION=0.12.6.1-3 +ARG WKHTMLTOPDF_DISTRO=bookworm +RUN if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \ + && if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \ + && downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \ + && wget -q https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \ + && dpkg -i $downloaded_file \ + && rm $downloaded_file + +# Create new user with home directory, improve docker compatibility with UID/GID 1000, +# add user to sudo group, allow passwordless sudo, switch to that user +# and change directory to user home directory +RUN groupadd -g 1000 frappe \ + && useradd --no-log-init -r -m -u 1000 -g 1000 -G sudo frappe \ + && echo "frappe ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +USER frappe +WORKDIR /home/frappe + +# Install Python via pyenv +ENV PYTHON_VERSION_V14=3.10.13 +ENV PYTHON_VERSION=3.11.6 +ENV PYENV_ROOT=/home/frappe/.pyenv +ENV PATH=$PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH + +# From https://github.com/pyenv/pyenv#basic-github-checkout +RUN git clone --depth 1 https://github.com/pyenv/pyenv.git .pyenv \ + && pyenv install $PYTHON_VERSION_V14 \ + && pyenv install $PYTHON_VERSION \ + && PYENV_VERSION=$PYTHON_VERSION_V14 pip install --no-cache-dir virtualenv \ + && PYENV_VERSION=$PYTHON_VERSION pip install --no-cache-dir virtualenv \ + && pyenv global $PYTHON_VERSION $PYTHON_VERSION_v14 \ + && sed -Ei -e '/^([^#]|$)/ {a export PYENV_ROOT="/home/frappe/.pyenv" a export PATH="$PYENV_ROOT/bin:$PATH" a ' -e ':a' -e '$!{n;ba};}' ~/.profile \ + && echo 'eval "$(pyenv init --path)"' >>~/.profile \ + && echo 'eval "$(pyenv init -)"' >>~/.bashrc + +# Clone and install bench in the local user home directory +# For development, bench source is located in ~/.bench +ENV PATH=/home/frappe/.local/bin:$PATH +# Skip editable-bench warning +# https://github.com/frappe/bench/commit/20560c97c4246b2480d7358c722bc9ad13606138 +RUN git clone ${GIT_REPO} --depth 1 -b ${GIT_BRANCH} .bench \ + && pip install --no-cache-dir --user -e .bench \ + && echo "export PATH=/home/frappe/.local/bin:\$PATH" >>/home/frappe/.bashrc \ + && echo "export BENCH_DEVELOPER=1" >>/home/frappe/.bashrc + +# Install Node via nvm +ENV NODE_VERSION_14=16.20.2 +ENV NODE_VERSION=20.19.2 +ENV NVM_DIR=/home/frappe/.nvm +ENV PATH=${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH} + +RUN wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \ + && . ${NVM_DIR}/nvm.sh \ + && nvm install ${NODE_VERSION_14} \ + && nvm use v${NODE_VERSION_14} \ + && npm install -g yarn \ + && nvm install ${NODE_VERSION} \ + && nvm use v${NODE_VERSION} \ + && npm install -g yarn \ + && nvm alias default v${NODE_VERSION} \ + && rm -rf ${NVM_DIR}/.cache \ + && echo 'export NVM_DIR="/home/frappe/.nvm"' >>~/.bashrc \ + && echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >> ~/.bashrc \ + && echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >> ~/.bashrc + + +EXPOSE 8000-8005 9000-9005 6787 + +FROM bench AS bench-test + +# Print version and verify bashrc is properly sourced so that everything works +# in the interactive shell and Dockerfile + +RUN node --version \ + && npm --version \ + && yarn --version \ + && bench --help + +RUN bash -c "node --version" \ + && bash -c "npm --version" \ + && bash -c "yarn --version" \ + && bash -c "bench --help" diff --git a/images/custom/Containerfile b/images/custom/Containerfile index 06182d42..2b75ac48 100644 --- a/images/custom/Containerfile +++ b/images/custom/Containerfile @@ -1,161 +1,161 @@ -ARG PYTHON_VERSION=3.11.6 -ARG DEBIAN_BASE=bookworm -FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base - -COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template -COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh - -ARG WKHTMLTOPDF_VERSION=0.12.6.1-3 -ARG WKHTMLTOPDF_DISTRO=bookworm -ARG NODE_VERSION=20.19.2 -ENV NVM_DIR=/home/frappe/.nvm -ENV PATH=${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH} - -RUN useradd -ms /bin/bash frappe \ - && apt-get update \ - && apt-get install --no-install-recommends -y \ - curl \ - git \ - vim \ - nginx \ - gettext-base \ - file \ - # weasyprint dependencies - libpango-1.0-0 \ - libharfbuzz0b \ - libpangoft2-1.0-0 \ - libpangocairo-1.0-0 \ - # For backups - restic \ - gpg \ - # MariaDB - mariadb-client \ - less \ - # Postgres - libpq-dev \ - postgresql-client \ - # For healthcheck - wait-for-it \ - jq \ - # For MIME type detection - media-types \ - # NodeJS - && mkdir -p ${NVM_DIR} \ - && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \ - && . ${NVM_DIR}/nvm.sh \ - && nvm install ${NODE_VERSION} \ - && nvm use v${NODE_VERSION} \ - && npm install -g yarn \ - && nvm alias default v${NODE_VERSION} \ - && rm -rf ${NVM_DIR}/.cache \ - && echo 'export NVM_DIR="/home/frappe/.nvm"' >>/home/frappe/.bashrc \ - && echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >>/home/frappe/.bashrc \ - && echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/home/frappe/.bashrc \ - # Install wkhtmltopdf with patched qt - && if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \ - && if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \ - && downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \ - && curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \ - && apt-get install -y ./$downloaded_file \ - && rm $downloaded_file \ - # Clean up - && rm -rf /var/lib/apt/lists/* \ - && rm -fr /etc/nginx/sites-enabled/default \ - && pip3 install frappe-bench \ - # Fixes for non-root nginx and logs to stdout - && sed -i '/user www-data/d' /etc/nginx/nginx.conf \ - && ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log \ - && touch /run/nginx.pid \ - && chown -R frappe:frappe /etc/nginx/conf.d \ - && chown -R frappe:frappe /etc/nginx/nginx.conf \ - && chown -R frappe:frappe /var/log/nginx \ - && chown -R frappe:frappe /var/lib/nginx \ - && chown -R frappe:frappe /run/nginx.pid \ - && chmod 755 /usr/local/bin/nginx-entrypoint.sh \ - && chmod 644 /templates/nginx/frappe.conf.template - -FROM base AS builder - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ - # For frappe framework - wget \ - #for building arm64 binaries - libcairo2-dev \ - libpango1.0-dev \ - libjpeg-dev \ - libgif-dev \ - librsvg2-dev \ - # For psycopg2 - libpq-dev \ - # Other - libffi-dev \ - liblcms2-dev \ - libldap2-dev \ - libmariadb-dev \ - libsasl2-dev \ - libtiff5-dev \ - libwebp-dev \ - pkg-config \ - redis-tools \ - rlwrap \ - tk8.6-dev \ - cron \ - # For pandas - gcc \ - build-essential \ - libbz2-dev \ - && rm -rf /var/lib/apt/lists/* - -# apps.json includes -ARG APPS_JSON_BASE64 -RUN if [ -n "${APPS_JSON_BASE64}" ]; then \ - mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \ - fi - -USER frappe - -ARG FRAPPE_BRANCH=version-15 -ARG FRAPPE_PATH=https://github.com/frappe/frappe -RUN export APP_INSTALL_ARGS="" && \ - if [ -n "${APPS_JSON_BASE64}" ]; then \ - export APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \ - fi && \ - bench init ${APP_INSTALL_ARGS}\ - --frappe-branch=${FRAPPE_BRANCH} \ - --frappe-path=${FRAPPE_PATH} \ - --no-procfile \ - --no-backups \ - --skip-redis-config-generation \ - --verbose \ - /home/frappe/frappe-bench && \ - cd /home/frappe/frappe-bench && \ - echo "{}" > sites/common_site_config.json && \ - find apps -mindepth 1 -path "*/.git" | xargs rm -fr - -FROM base AS backend - -USER frappe - -COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench - -WORKDIR /home/frappe/frappe-bench - -VOLUME [ \ - "/home/frappe/frappe-bench/sites", \ - "/home/frappe/frappe-bench/sites/assets", \ - "/home/frappe/frappe-bench/logs" \ -] - -CMD [ \ - "/home/frappe/frappe-bench/env/bin/gunicorn", \ - "--chdir=/home/frappe/frappe-bench/sites", \ - "--bind=0.0.0.0:8000", \ - "--threads=4", \ - "--workers=2", \ - "--worker-class=gthread", \ - "--worker-tmp-dir=/dev/shm", \ - "--timeout=120", \ - "--preload", \ - "frappe.app:application" \ -] +ARG PYTHON_VERSION=3.11.6 +ARG DEBIAN_BASE=bookworm +FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base + +COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template +COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh + +ARG WKHTMLTOPDF_VERSION=0.12.6.1-3 +ARG WKHTMLTOPDF_DISTRO=bookworm +ARG NODE_VERSION=20.19.2 +ENV NVM_DIR=/home/frappe/.nvm +ENV PATH=${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH} + +RUN useradd -ms /bin/bash frappe \ + && apt-get update \ + && apt-get install --no-install-recommends -y \ + curl \ + git \ + vim \ + nginx \ + gettext-base \ + file \ + # weasyprint dependencies + libpango-1.0-0 \ + libharfbuzz0b \ + libpangoft2-1.0-0 \ + libpangocairo-1.0-0 \ + # For backups + restic \ + gpg \ + # MariaDB + mariadb-client \ + less \ + # Postgres + libpq-dev \ + postgresql-client \ + # For healthcheck + wait-for-it \ + jq \ + # For MIME type detection + media-types \ + # NodeJS + && mkdir -p ${NVM_DIR} \ + && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \ + && . ${NVM_DIR}/nvm.sh \ + && nvm install ${NODE_VERSION} \ + && nvm use v${NODE_VERSION} \ + && npm install -g yarn \ + && nvm alias default v${NODE_VERSION} \ + && rm -rf ${NVM_DIR}/.cache \ + && echo 'export NVM_DIR="/home/frappe/.nvm"' >>/home/frappe/.bashrc \ + && echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >>/home/frappe/.bashrc \ + && echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/home/frappe/.bashrc \ + # Install wkhtmltopdf with patched qt + && if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \ + && if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \ + && downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \ + && curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \ + && apt-get install -y ./$downloaded_file \ + && rm $downloaded_file \ + # Clean up + && rm -rf /var/lib/apt/lists/* \ + && rm -fr /etc/nginx/sites-enabled/default \ + && pip3 install frappe-bench \ + # Fixes for non-root nginx and logs to stdout + && sed -i '/user www-data/d' /etc/nginx/nginx.conf \ + && ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log \ + && touch /run/nginx.pid \ + && chown -R frappe:frappe /etc/nginx/conf.d \ + && chown -R frappe:frappe /etc/nginx/nginx.conf \ + && chown -R frappe:frappe /var/log/nginx \ + && chown -R frappe:frappe /var/lib/nginx \ + && chown -R frappe:frappe /run/nginx.pid \ + && chmod 755 /usr/local/bin/nginx-entrypoint.sh \ + && chmod 644 /templates/nginx/frappe.conf.template + +FROM base AS builder + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + # For frappe framework + wget \ + #for building arm64 binaries + libcairo2-dev \ + libpango1.0-dev \ + libjpeg-dev \ + libgif-dev \ + librsvg2-dev \ + # For psycopg2 + libpq-dev \ + # Other + libffi-dev \ + liblcms2-dev \ + libldap2-dev \ + libmariadb-dev \ + libsasl2-dev \ + libtiff5-dev \ + libwebp-dev \ + pkg-config \ + redis-tools \ + rlwrap \ + tk8.6-dev \ + cron \ + # For pandas + gcc \ + build-essential \ + libbz2-dev \ + && rm -rf /var/lib/apt/lists/* + +# apps.json includes +ARG APPS_JSON_BASE64 +RUN if [ -n "${APPS_JSON_BASE64}" ]; then \ + mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \ + fi + +USER frappe + +ARG FRAPPE_BRANCH=version-15 +ARG FRAPPE_PATH=https://github.com/frappe/frappe +RUN export APP_INSTALL_ARGS="" && \ + if [ -n "${APPS_JSON_BASE64}" ]; then \ + export APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \ + fi && \ + bench init ${APP_INSTALL_ARGS}\ + --frappe-branch=${FRAPPE_BRANCH} \ + --frappe-path=${FRAPPE_PATH} \ + --no-procfile \ + --no-backups \ + --skip-redis-config-generation \ + --verbose \ + /home/frappe/frappe-bench && \ + cd /home/frappe/frappe-bench && \ + echo "{}" > sites/common_site_config.json && \ + find apps -mindepth 1 -path "*/.git" | xargs rm -fr + +FROM base AS backend + +USER frappe + +COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench + +WORKDIR /home/frappe/frappe-bench + +VOLUME [ \ + "/home/frappe/frappe-bench/sites", \ + "/home/frappe/frappe-bench/sites/assets", \ + "/home/frappe/frappe-bench/logs" \ +] + +CMD [ \ + "/home/frappe/frappe-bench/env/bin/gunicorn", \ + "--chdir=/home/frappe/frappe-bench/sites", \ + "--bind=0.0.0.0:8000", \ + "--threads=4", \ + "--workers=2", \ + "--worker-class=gthread", \ + "--worker-tmp-dir=/dev/shm", \ + "--timeout=120", \ + "--preload", \ + "frappe.app:application" \ +] diff --git a/images/layered/Containerfile b/images/layered/Containerfile index 12a089ee..edd6af99 100644 --- a/images/layered/Containerfile +++ b/images/layered/Containerfile @@ -1,58 +1,58 @@ -ARG FRAPPE_BRANCH=version-15 - -FROM frappe/build:${FRAPPE_BRANCH} AS builder - -ARG FRAPPE_BRANCH=version-15 -ARG FRAPPE_PATH=https://github.com/frappe/frappe -ARG APPS_JSON_BASE64 - -USER root - -RUN if [ -n "${APPS_JSON_BASE64}" ]; then \ - mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \ - fi - -USER frappe - -RUN export APP_INSTALL_ARGS="" && \ - if [ -n "${APPS_JSON_BASE64}" ]; then \ - export APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \ - fi && \ - bench init ${APP_INSTALL_ARGS}\ - --frappe-branch=${FRAPPE_BRANCH} \ - --frappe-path=${FRAPPE_PATH} \ - --no-procfile \ - --no-backups \ - --skip-redis-config-generation \ - --verbose \ - /home/frappe/frappe-bench && \ - cd /home/frappe/frappe-bench && \ - echo "{}" > sites/common_site_config.json && \ - find apps -mindepth 1 -path "*/.git" | xargs rm -fr - -FROM frappe/base:${FRAPPE_BRANCH} AS backend - -USER frappe - -COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench - -WORKDIR /home/frappe/frappe-bench - -VOLUME [ \ - "/home/frappe/frappe-bench/sites", \ - "/home/frappe/frappe-bench/sites/assets", \ - "/home/frappe/frappe-bench/logs" \ -] - -CMD [ \ - "/home/frappe/frappe-bench/env/bin/gunicorn", \ - "--chdir=/home/frappe/frappe-bench/sites", \ - "--bind=0.0.0.0:8000", \ - "--threads=4", \ - "--workers=2", \ - "--worker-class=gthread", \ - "--worker-tmp-dir=/dev/shm", \ - "--timeout=120", \ - "--preload", \ - "frappe.app:application" \ -] +ARG FRAPPE_BRANCH=version-15 + +FROM frappe/build:${FRAPPE_BRANCH} AS builder + +ARG FRAPPE_BRANCH=version-15 +ARG FRAPPE_PATH=https://github.com/frappe/frappe +ARG APPS_JSON_BASE64 + +USER root + +RUN if [ -n "${APPS_JSON_BASE64}" ]; then \ + mkdir /opt/frappe && echo "${APPS_JSON_BASE64}" | base64 -d > /opt/frappe/apps.json; \ + fi + +USER frappe + +RUN export APP_INSTALL_ARGS="" && \ + if [ -n "${APPS_JSON_BASE64}" ]; then \ + export APP_INSTALL_ARGS="--apps_path=/opt/frappe/apps.json"; \ + fi && \ + bench init ${APP_INSTALL_ARGS}\ + --frappe-branch=${FRAPPE_BRANCH} \ + --frappe-path=${FRAPPE_PATH} \ + --no-procfile \ + --no-backups \ + --skip-redis-config-generation \ + --verbose \ + /home/frappe/frappe-bench && \ + cd /home/frappe/frappe-bench && \ + echo "{}" > sites/common_site_config.json && \ + find apps -mindepth 1 -path "*/.git" | xargs rm -fr + +FROM frappe/base:${FRAPPE_BRANCH} AS backend + +USER frappe + +COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench + +WORKDIR /home/frappe/frappe-bench + +VOLUME [ \ + "/home/frappe/frappe-bench/sites", \ + "/home/frappe/frappe-bench/sites/assets", \ + "/home/frappe/frappe-bench/logs" \ +] + +CMD [ \ + "/home/frappe/frappe-bench/env/bin/gunicorn", \ + "--chdir=/home/frappe/frappe-bench/sites", \ + "--bind=0.0.0.0:8000", \ + "--threads=4", \ + "--workers=2", \ + "--worker-class=gthread", \ + "--worker-tmp-dir=/dev/shm", \ + "--timeout=120", \ + "--preload", \ + "frappe.app:application" \ +] diff --git a/images/production/Containerfile b/images/production/Containerfile index 563bc763..5af7e28b 100644 --- a/images/production/Containerfile +++ b/images/production/Containerfile @@ -1,150 +1,150 @@ -ARG PYTHON_VERSION=3.11.6 -ARG DEBIAN_BASE=bookworm -FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base - -ARG WKHTMLTOPDF_VERSION=0.12.6.1-3 -ARG WKHTMLTOPDF_DISTRO=bookworm -ARG NODE_VERSION=20.19.2 -ENV NVM_DIR=/home/frappe/.nvm -ENV PATH=${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH} - -RUN useradd -ms /bin/bash frappe \ - && apt-get update \ - && apt-get install --no-install-recommends -y \ - curl \ - git \ - vim \ - nginx \ - gettext-base \ - file \ - # weasyprint dependencies - libpango-1.0-0 \ - libharfbuzz0b \ - libpangoft2-1.0-0 \ - libpangocairo-1.0-0 \ - # For backups - restic \ - gpg \ - # MariaDB - mariadb-client \ - less \ - # Postgres - libpq-dev \ - postgresql-client \ - # For healthcheck - wait-for-it \ - jq \ - # For MIME type detection - media-types \ - # NodeJS - && mkdir -p ${NVM_DIR} \ - && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \ - && . ${NVM_DIR}/nvm.sh \ - && nvm install ${NODE_VERSION} \ - && nvm use v${NODE_VERSION} \ - && npm install -g yarn \ - && nvm alias default v${NODE_VERSION} \ - && rm -rf ${NVM_DIR}/.cache \ - && echo 'export NVM_DIR="/home/frappe/.nvm"' >>/home/frappe/.bashrc \ - && echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >>/home/frappe/.bashrc \ - && echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/home/frappe/.bashrc \ - # Install wkhtmltopdf with patched qt - && if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \ - && if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \ - && downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \ - && curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \ - && apt-get install -y ./$downloaded_file \ - && rm $downloaded_file \ - # Clean up - && rm -rf /var/lib/apt/lists/* \ - && rm -fr /etc/nginx/sites-enabled/default \ - && pip3 install frappe-bench \ - # Fixes for non-root nginx and logs to stdout - && sed -i '/user www-data/d' /etc/nginx/nginx.conf \ - && ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log \ - && touch /run/nginx.pid \ - && chown -R frappe:frappe /etc/nginx/conf.d \ - && chown -R frappe:frappe /etc/nginx/nginx.conf \ - && chown -R frappe:frappe /var/log/nginx \ - && chown -R frappe:frappe /var/lib/nginx \ - && chown -R frappe:frappe /run/nginx.pid - -COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template -COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh - -FROM base AS build - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ - # For frappe framework - wget \ - # For psycopg2 - libpq-dev \ - # Other - libffi-dev \ - liblcms2-dev \ - libldap2-dev \ - libmariadb-dev \ - libsasl2-dev \ - libtiff5-dev \ - libwebp-dev \ - pkg-config \ - redis-tools \ - rlwrap \ - tk8.6-dev \ - cron \ - # For pandas - gcc \ - build-essential \ - libbz2-dev \ - && rm -rf /var/lib/apt/lists/* - -USER frappe - -FROM build AS builder - -ARG FRAPPE_BRANCH=version-15 -ARG FRAPPE_PATH=https://github.com/frappe/frappe -ARG ERPNEXT_REPO=https://github.com/frappe/erpnext -ARG ERPNEXT_BRANCH=version-15 -RUN bench init \ - --frappe-branch=${FRAPPE_BRANCH} \ - --frappe-path=${FRAPPE_PATH} \ - --no-procfile \ - --no-backups \ - --skip-redis-config-generation \ - --verbose \ - /home/frappe/frappe-bench && \ - cd /home/frappe/frappe-bench && \ - bench get-app --branch=${ERPNEXT_BRANCH} --resolve-deps erpnext ${ERPNEXT_REPO} && \ - echo "{}" > sites/common_site_config.json && \ - find apps -mindepth 1 -path "*/.git" | xargs rm -fr - -FROM base AS erpnext - -USER frappe - -RUN echo "echo \"Commands restricted in prodution container, Read FAQ before you proceed: https://frappe.io/ctr-faq\"" >> ~/.bashrc - -COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench - -WORKDIR /home/frappe/frappe-bench - -VOLUME [ \ - "/home/frappe/frappe-bench/sites", \ - "/home/frappe/frappe-bench/sites/assets", \ - "/home/frappe/frappe-bench/logs" \ -] - -CMD [ \ - "/home/frappe/frappe-bench/env/bin/gunicorn", \ - "--chdir=/home/frappe/frappe-bench/sites", \ - "--bind=0.0.0.0:8000", \ - "--threads=4", \ - "--workers=2", \ - "--worker-class=gthread", \ - "--worker-tmp-dir=/dev/shm", \ - "--timeout=120", \ - "--preload", \ - "frappe.app:application" \ -] +ARG PYTHON_VERSION=3.11.6 +ARG DEBIAN_BASE=bookworm +FROM python:${PYTHON_VERSION}-slim-${DEBIAN_BASE} AS base + +ARG WKHTMLTOPDF_VERSION=0.12.6.1-3 +ARG WKHTMLTOPDF_DISTRO=bookworm +ARG NODE_VERSION=20.19.2 +ENV NVM_DIR=/home/frappe/.nvm +ENV PATH=${NVM_DIR}/versions/node/v${NODE_VERSION}/bin/:${PATH} + +RUN useradd -ms /bin/bash frappe \ + && apt-get update \ + && apt-get install --no-install-recommends -y \ + curl \ + git \ + vim \ + nginx \ + gettext-base \ + file \ + # weasyprint dependencies + libpango-1.0-0 \ + libharfbuzz0b \ + libpangoft2-1.0-0 \ + libpangocairo-1.0-0 \ + # For backups + restic \ + gpg \ + # MariaDB + mariadb-client \ + less \ + # Postgres + libpq-dev \ + postgresql-client \ + # For healthcheck + wait-for-it \ + jq \ + # For MIME type detection + media-types \ + # NodeJS + && mkdir -p ${NVM_DIR} \ + && curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash \ + && . ${NVM_DIR}/nvm.sh \ + && nvm install ${NODE_VERSION} \ + && nvm use v${NODE_VERSION} \ + && npm install -g yarn \ + && nvm alias default v${NODE_VERSION} \ + && rm -rf ${NVM_DIR}/.cache \ + && echo 'export NVM_DIR="/home/frappe/.nvm"' >>/home/frappe/.bashrc \ + && echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm' >>/home/frappe/.bashrc \ + && echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion' >>/home/frappe/.bashrc \ + # Install wkhtmltopdf with patched qt + && if [ "$(uname -m)" = "aarch64" ]; then export ARCH=arm64; fi \ + && if [ "$(uname -m)" = "x86_64" ]; then export ARCH=amd64; fi \ + && downloaded_file=wkhtmltox_${WKHTMLTOPDF_VERSION}.${WKHTMLTOPDF_DISTRO}_${ARCH}.deb \ + && curl -sLO https://github.com/wkhtmltopdf/packaging/releases/download/$WKHTMLTOPDF_VERSION/$downloaded_file \ + && apt-get install -y ./$downloaded_file \ + && rm $downloaded_file \ + # Clean up + && rm -rf /var/lib/apt/lists/* \ + && rm -fr /etc/nginx/sites-enabled/default \ + && pip3 install frappe-bench \ + # Fixes for non-root nginx and logs to stdout + && sed -i '/user www-data/d' /etc/nginx/nginx.conf \ + && ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log \ + && touch /run/nginx.pid \ + && chown -R frappe:frappe /etc/nginx/conf.d \ + && chown -R frappe:frappe /etc/nginx/nginx.conf \ + && chown -R frappe:frappe /var/log/nginx \ + && chown -R frappe:frappe /var/lib/nginx \ + && chown -R frappe:frappe /run/nginx.pid + +COPY resources/nginx-template.conf /templates/nginx/frappe.conf.template +COPY resources/nginx-entrypoint.sh /usr/local/bin/nginx-entrypoint.sh + +FROM base AS build + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + # For frappe framework + wget \ + # For psycopg2 + libpq-dev \ + # Other + libffi-dev \ + liblcms2-dev \ + libldap2-dev \ + libmariadb-dev \ + libsasl2-dev \ + libtiff5-dev \ + libwebp-dev \ + pkg-config \ + redis-tools \ + rlwrap \ + tk8.6-dev \ + cron \ + # For pandas + gcc \ + build-essential \ + libbz2-dev \ + && rm -rf /var/lib/apt/lists/* + +USER frappe + +FROM build AS builder + +ARG FRAPPE_BRANCH=version-15 +ARG FRAPPE_PATH=https://github.com/frappe/frappe +ARG ERPNEXT_REPO=https://github.com/frappe/erpnext +ARG ERPNEXT_BRANCH=version-15 +RUN bench init \ + --frappe-branch=${FRAPPE_BRANCH} \ + --frappe-path=${FRAPPE_PATH} \ + --no-procfile \ + --no-backups \ + --skip-redis-config-generation \ + --verbose \ + /home/frappe/frappe-bench && \ + cd /home/frappe/frappe-bench && \ + bench get-app --branch=${ERPNEXT_BRANCH} --resolve-deps erpnext ${ERPNEXT_REPO} && \ + echo "{}" > sites/common_site_config.json && \ + find apps -mindepth 1 -path "*/.git" | xargs rm -fr + +FROM base AS erpnext + +USER frappe + +RUN echo "echo \"Commands restricted in prodution container, Read FAQ before you proceed: https://frappe.io/ctr-faq\"" >> ~/.bashrc + +COPY --from=builder --chown=frappe:frappe /home/frappe/frappe-bench /home/frappe/frappe-bench + +WORKDIR /home/frappe/frappe-bench + +VOLUME [ \ + "/home/frappe/frappe-bench/sites", \ + "/home/frappe/frappe-bench/sites/assets", \ + "/home/frappe/frappe-bench/logs" \ +] + +CMD [ \ + "/home/frappe/frappe-bench/env/bin/gunicorn", \ + "--chdir=/home/frappe/frappe-bench/sites", \ + "--bind=0.0.0.0:8000", \ + "--threads=4", \ + "--workers=2", \ + "--worker-class=gthread", \ + "--worker-tmp-dir=/dev/shm", \ + "--timeout=120", \ + "--preload", \ + "frappe.app:application" \ +] diff --git a/install_x11_deps.sh b/install_x11_deps.sh index a66b2042..6172dd14 100755 --- a/install_x11_deps.sh +++ b/install_x11_deps.sh @@ -1,104 +1,104 @@ -#!/bin/bash -set -e -# This script configures X11 forwarding for Linux and macOS systems. -# It installs X11, Openbox (on Linux), and checks for XQuartz (on macOS). -# It also updates the sshd_config file to enable X11Forwarding and restarts the SSH service. - -# Check if the script is running with root privileges -if [ "$EUID" -ne 0 ]; then - echo "Error: This script requires root privileges. Please run it as a superuser" - exit 1 -fi - -# Function to restart SSH service (Linux) -restart_ssh_linux() { - if command -v service >/dev/null 2>&1; then - sudo service ssh restart - else - sudo systemctl restart ssh - fi -} - -# Function to restart SSH service (macOS) -restart_ssh_macos() { - launchctl stop com.openssh.sshd - launchctl start com.openssh.sshd -} - -update_x11_forwarding() { - if grep -q "X11Forwarding yes" /etc/ssh/sshd_config; then - echo "X11Forwarding is already set to 'yes' in ssh_config." - else - if [[ "$OSTYPE" == "linux-gnu" ]]; then - # Linux: Use sed for Linux - sudo sed -i 's/#\?X11Forwarding.*/X11Forwarding yes/' /etc/ssh/sshd_config - elif [[ "$OSTYPE" == "darwin"* ]]; then - # macOS: Use sed for macOS - sudo sed -i -E 's/#X11Forwarding.*/X11Forwarding yes/' /etc/ssh/sshd_config - restart_ssh_macos - fi - if [[ "$OSTYPE" == "linux-gnu" ]]; then - restart_ssh_linux - fi - fi -} - -# Determine the operating system -if [[ "$OSTYPE" == "linux-gnu" ]]; then - # Linux - if command -v startx >/dev/null 2>&1; then - echo "X11 is already installed." - else - # Check which package manager is available - if command -v apt-get >/dev/null 2>&1; then - install_command="sudo apt-get update && sudo apt-get install xorg openbox" - elif command -v dnf >/dev/null 2>&1; then - install_command="sudo dnf install xorg-x11-server-Xorg openbox" - else - echo "Error: Unable to determine the package manager. Manual installation required." - exit 1 - fi - fi - # Check if the installation command is defined - if [ -n "$install_command" ]; then - # Execute the installation command - if $install_command; then - echo "X11 and Openbox have been successfully installed." - else - echo "Error: Failed to install X11 and Openbox." - exit 1 - fi - else - echo "Error: Unsupported package manager." - exit 1 - fi - - # Call the function to update X11Forwarding - update_x11_forwarding - - # Get the IP address of the host dynamically - host_ip=$(hostname -I | awk '{print $1}') - xhost + "$host_ip" && xhost + local: - # Set the DISPLAY variable to the host IP - export DISPLAY="$host_ip:0.0" - echo "DISPLAY variable set to $DISPLAY" - -elif [[ "$OSTYPE" == "darwin"* ]]; then - # macOS - if command -v xquartz >/dev/null 2>&1; then - echo "XQuartz is already installed." - else - echo "Error: XQuartz is required for X11 forwarding on macOS. Please install XQuartz manually." - exit 1 - fi - - # Call the function to update X11Forwarding - update_x11_forwarding - - # Export the DISPLAY variable for macOS - export DISPLAY=:0 - echo "DISPLAY variable set to $DISPLAY" -else - echo "Error: Unsupported operating system." - exit 1 -fi +#!/bin/bash +set -e +# This script configures X11 forwarding for Linux and macOS systems. +# It installs X11, Openbox (on Linux), and checks for XQuartz (on macOS). +# It also updates the sshd_config file to enable X11Forwarding and restarts the SSH service. + +# Check if the script is running with root privileges +if [ "$EUID" -ne 0 ]; then + echo "Error: This script requires root privileges. Please run it as a superuser" + exit 1 +fi + +# Function to restart SSH service (Linux) +restart_ssh_linux() { + if command -v service >/dev/null 2>&1; then + sudo service ssh restart + else + sudo systemctl restart ssh + fi +} + +# Function to restart SSH service (macOS) +restart_ssh_macos() { + launchctl stop com.openssh.sshd + launchctl start com.openssh.sshd +} + +update_x11_forwarding() { + if grep -q "X11Forwarding yes" /etc/ssh/sshd_config; then + echo "X11Forwarding is already set to 'yes' in ssh_config." + else + if [[ "$OSTYPE" == "linux-gnu" ]]; then + # Linux: Use sed for Linux + sudo sed -i 's/#\?X11Forwarding.*/X11Forwarding yes/' /etc/ssh/sshd_config + elif [[ "$OSTYPE" == "darwin"* ]]; then + # macOS: Use sed for macOS + sudo sed -i -E 's/#X11Forwarding.*/X11Forwarding yes/' /etc/ssh/sshd_config + restart_ssh_macos + fi + if [[ "$OSTYPE" == "linux-gnu" ]]; then + restart_ssh_linux + fi + fi +} + +# Determine the operating system +if [[ "$OSTYPE" == "linux-gnu" ]]; then + # Linux + if command -v startx >/dev/null 2>&1; then + echo "X11 is already installed." + else + # Check which package manager is available + if command -v apt-get >/dev/null 2>&1; then + install_command="sudo apt-get update && sudo apt-get install xorg openbox" + elif command -v dnf >/dev/null 2>&1; then + install_command="sudo dnf install xorg-x11-server-Xorg openbox" + else + echo "Error: Unable to determine the package manager. Manual installation required." + exit 1 + fi + fi + # Check if the installation command is defined + if [ -n "$install_command" ]; then + # Execute the installation command + if $install_command; then + echo "X11 and Openbox have been successfully installed." + else + echo "Error: Failed to install X11 and Openbox." + exit 1 + fi + else + echo "Error: Unsupported package manager." + exit 1 + fi + + # Call the function to update X11Forwarding + update_x11_forwarding + + # Get the IP address of the host dynamically + host_ip=$(hostname -I | awk '{print $1}') + xhost + "$host_ip" && xhost + local: + # Set the DISPLAY variable to the host IP + export DISPLAY="$host_ip:0.0" + echo "DISPLAY variable set to $DISPLAY" + +elif [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + if command -v xquartz >/dev/null 2>&1; then + echo "XQuartz is already installed." + else + echo "Error: XQuartz is required for X11 forwarding on macOS. Please install XQuartz manually." + exit 1 + fi + + # Call the function to update X11Forwarding + update_x11_forwarding + + # Export the DISPLAY variable for macOS + export DISPLAY=:0 + echo "DISPLAY variable set to $DISPLAY" +else + echo "Error: Unsupported operating system." + exit 1 +fi diff --git a/overrides/compose.backup-cron.yaml b/overrides/compose.backup-cron.yaml index b7e9c272..aa36fc81 100644 --- a/overrides/compose.backup-cron.yaml +++ b/overrides/compose.backup-cron.yaml @@ -1,15 +1,15 @@ -services: - cron: - image: mcuadros/ofelia:latest - depends_on: - - scheduler - command: daemon --docker - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - scheduler: - labels: - ofelia.enabled: "true" - ofelia.job-exec.datecron.schedule: "${BACKUP_CRONSTRING:-@every 6h}" - ofelia.job-exec.datecron.command: "bench --site all backup" - ofelia.job-exec.datecron.user: "frappe" +services: + cron: + image: mcuadros/ofelia:latest + depends_on: + - scheduler + command: daemon --docker + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + + scheduler: + labels: + ofelia.enabled: "true" + ofelia.job-exec.datecron.schedule: "${BACKUP_CRONSTRING:-@every 6h}" + ofelia.job-exec.datecron.command: "bench --site all backup" + ofelia.job-exec.datecron.user: "frappe" diff --git a/overrides/compose.custom-domain-ssl.yaml b/overrides/compose.custom-domain-ssl.yaml index 5aa1d0ea..11a8985d 100644 --- a/overrides/compose.custom-domain-ssl.yaml +++ b/overrides/compose.custom-domain-ssl.yaml @@ -1,5 +1,5 @@ -services: - custom-domain: - labels: - - traefik.http.routers.${ROUTER}.entrypoints=http,https - - traefik.http.routers.${ROUTER}.tls.certresolver=le +services: + custom-domain: + labels: + - traefik.http.routers.${ROUTER}.entrypoints=http,https + - traefik.http.routers.${ROUTER}.tls.certresolver=le diff --git a/overrides/compose.custom-domain.yaml b/overrides/compose.custom-domain.yaml index 5746a325..94a0747d 100644 --- a/overrides/compose.custom-domain.yaml +++ b/overrides/compose.custom-domain.yaml @@ -1,31 +1,31 @@ -version: "3.3" - -services: - custom-domain: - image: caddy:2 - command: - - caddy - - reverse-proxy - - --to - - frontend:8080 - - --from - - :2016 - labels: - - traefik.enable=true - - traefik.docker.network=traefik-public - - traefik.http.services.${ROUTER?ROUTER not set}.loadbalancer.server.port=2016 - - traefik.http.routers.${ROUTER}.service=${ROUTER} - - traefik.http.routers.${ROUTER}.entrypoints=http - - traefik.http.routers.${ROUTER}.rule=Host(${SITES?SITES not set}) - - traefik.http.middlewares.${ROUTER}.headers.customrequestheaders.Host=${BASE_SITE?BASE_SITE not set} - - traefik.http.routers.${ROUTER}.middlewares=${ROUTER} - networks: - - traefik-public - - bench-network - -networks: - traefik-public: - external: true - bench-network: - name: ${BENCH_NETWORK?BENCH_NETWORK not set} - external: true +version: "3.3" + +services: + custom-domain: + image: caddy:2 + command: + - caddy + - reverse-proxy + - --to + - frontend:8080 + - --from + - :2016 + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.http.services.${ROUTER?ROUTER not set}.loadbalancer.server.port=2016 + - traefik.http.routers.${ROUTER}.service=${ROUTER} + - traefik.http.routers.${ROUTER}.entrypoints=http + - traefik.http.routers.${ROUTER}.rule=Host(${SITES?SITES not set}) + - traefik.http.middlewares.${ROUTER}.headers.customrequestheaders.Host=${BASE_SITE?BASE_SITE not set} + - traefik.http.routers.${ROUTER}.middlewares=${ROUTER} + networks: + - traefik-public + - bench-network + +networks: + traefik-public: + external: true + bench-network: + name: ${BENCH_NETWORK?BENCH_NETWORK not set} + external: true diff --git a/overrides/compose.https.yaml b/overrides/compose.https.yaml index 9096e560..35a197f1 100644 --- a/overrides/compose.https.yaml +++ b/overrides/compose.https.yaml @@ -1,32 +1,32 @@ -services: - frontend: - labels: - - traefik.enable=true - - traefik.http.services.frontend.loadbalancer.server.port=8080 - - traefik.http.routers.frontend-http.entrypoints=websecure - - traefik.http.routers.frontend-http.tls.certresolver=main-resolver - - traefik.http.routers.frontend-http.rule=Host(${SITES:?List of sites not set}) - - proxy: - image: traefik:v2.11 - restart: unless-stopped - command: - - --providers.docker=true - - --providers.docker.exposedbydefault=false - - --entrypoints.web.address=:80 - - --entrypoints.web.http.redirections.entrypoint.to=websecure - - --entrypoints.web.http.redirections.entrypoint.scheme=https - - --entrypoints.websecure.address=:443 - - --certificatesResolvers.main-resolver.acme.httpChallenge=true - - --certificatesResolvers.main-resolver.acme.httpChallenge.entrypoint=web - - --certificatesResolvers.main-resolver.acme.email=${LETSENCRYPT_EMAIL:?No Let's Encrypt email set} - - --certificatesResolvers.main-resolver.acme.storage=/letsencrypt/acme.json - ports: - - ${HTTP_PUBLISH_PORT:-80}:80 - - ${HTTPS_PUBLISH_PORT:-443}:443 - volumes: - - cert-data:/letsencrypt - - /var/run/docker.sock:/var/run/docker.sock:ro - -volumes: - cert-data: +services: + frontend: + labels: + - traefik.enable=true + - traefik.http.services.frontend.loadbalancer.server.port=8080 + - traefik.http.routers.frontend-http.entrypoints=websecure + - traefik.http.routers.frontend-http.tls.certresolver=main-resolver + - traefik.http.routers.frontend-http.rule=Host(${SITES:?List of sites not set}) + + proxy: + image: traefik:v2.11 + restart: unless-stopped + command: + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --entrypoints.websecure.address=:443 + - --certificatesResolvers.main-resolver.acme.httpChallenge=true + - --certificatesResolvers.main-resolver.acme.httpChallenge.entrypoint=web + - --certificatesResolvers.main-resolver.acme.email=${LETSENCRYPT_EMAIL:?No Let's Encrypt email set} + - --certificatesResolvers.main-resolver.acme.storage=/letsencrypt/acme.json + ports: + - ${HTTP_PUBLISH_PORT:-80}:80 + - ${HTTPS_PUBLISH_PORT:-443}:443 + volumes: + - cert-data:/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock:ro + +volumes: + cert-data: diff --git a/overrides/compose.mariadb-secrets.yaml b/overrides/compose.mariadb-secrets.yaml index 2e904463..54a6c6e2 100644 --- a/overrides/compose.mariadb-secrets.yaml +++ b/overrides/compose.mariadb-secrets.yaml @@ -1,11 +1,11 @@ -services: - db: - environment: - MYSQL_ROOT_PASSWORD: !reset null - MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password - secrets: - - db_password - -secrets: - db_password: - file: ${DB_PASSWORD_SECRETS_FILE:?No db secret file set} +services: + db: + environment: + MYSQL_ROOT_PASSWORD: !reset null + MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password + secrets: + - db_password + +secrets: + db_password: + file: ${DB_PASSWORD_SECRETS_FILE:?No db secret file set} diff --git a/overrides/compose.mariadb-shared.yaml b/overrides/compose.mariadb-shared.yaml index 30d148e3..c57c3223 100644 --- a/overrides/compose.mariadb-shared.yaml +++ b/overrides/compose.mariadb-shared.yaml @@ -1,33 +1,33 @@ -version: "3.3" - -services: - database: - container_name: mariadb-database - image: mariadb:11.8 - restart: unless-stopped - healthcheck: - test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] - start_period: 5s - interval: 5s - timeout: 5s - retries: 5 - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed - environment: - MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-changeit} - MARIADB_AUTO_UPGRADE: 1 - volumes: - - db-data:/var/lib/mysql - networks: - - mariadb-network - -networks: - mariadb-network: - name: mariadb-network - external: false - -volumes: - db-data: +version: "3.3" + +services: + database: + container_name: mariadb-database + image: mariadb:11.8 + restart: unless-stopped + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + start_period: 5s + interval: 5s + timeout: 5s + retries: 5 + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + - --skip-innodb-read-only-compressed + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-changeit} + MARIADB_AUTO_UPGRADE: 1 + volumes: + - db-data:/var/lib/mysql + networks: + - mariadb-network + +networks: + mariadb-network: + name: mariadb-network + external: false + +volumes: + db-data: diff --git a/overrides/compose.mariadb.yaml b/overrides/compose.mariadb.yaml index 22d95ea5..eae23caf 100644 --- a/overrides/compose.mariadb.yaml +++ b/overrides/compose.mariadb.yaml @@ -1,31 +1,31 @@ -services: - configurator: - environment: - DB_HOST: db - DB_PORT: 3306 - depends_on: - db: - condition: service_healthy - - db: - image: mariadb:11.8 - healthcheck: - test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] - start_period: 5s - interval: 5s - timeout: 5s - retries: 5 - restart: unless-stopped - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 - environment: - MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-123} - MARIADB_AUTO_UPGRADE: 1 - volumes: - - db-data:/var/lib/mysql - -volumes: - db-data: +services: + configurator: + environment: + DB_HOST: db + DB_PORT: 3306 + depends_on: + db: + condition: service_healthy + + db: + image: mariadb:11.8 + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + start_period: 5s + interval: 5s + timeout: 5s + retries: 5 + restart: unless-stopped + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-123} + MARIADB_AUTO_UPGRADE: 1 + volumes: + - db-data:/var/lib/mysql + +volumes: + db-data: diff --git a/overrides/compose.multi-bench-ssl.yaml b/overrides/compose.multi-bench-ssl.yaml index 158d22bd..b5056cb7 100644 --- a/overrides/compose.multi-bench-ssl.yaml +++ b/overrides/compose.multi-bench-ssl.yaml @@ -1,14 +1,14 @@ -services: - frontend: - labels: - # ${ROUTER}-http to use the middleware to redirect to https - - traefik.http.routers.${ROUTER}-http.middlewares=https-redirect - # ${ROUTER}-https the actual router using HTTPS - # Uses the environment variable SITES - - traefik.http.routers.${ROUTER}-https.rule=Host(${SITES?SITES not set}) - - traefik.http.routers.${ROUTER}-https.entrypoints=https - - traefik.http.routers.${ROUTER}-https.tls=true - # Use the service ${ROUTER} with the frontend - - traefik.http.routers.${ROUTER}-https.service=${ROUTER} - # Use the "le" (Let's Encrypt) resolver created below - - traefik.http.routers.${ROUTER}-https.tls.certresolver=le +services: + frontend: + labels: + # ${ROUTER}-http to use the middleware to redirect to https + - traefik.http.routers.${ROUTER}-http.middlewares=https-redirect + # ${ROUTER}-https the actual router using HTTPS + # Uses the environment variable SITES + - traefik.http.routers.${ROUTER}-https.rule=Host(${SITES?SITES not set}) + - traefik.http.routers.${ROUTER}-https.entrypoints=https + - traefik.http.routers.${ROUTER}-https.tls=true + # Use the service ${ROUTER} with the frontend + - traefik.http.routers.${ROUTER}-https.service=${ROUTER} + # Use the "le" (Let's Encrypt) resolver created below + - traefik.http.routers.${ROUTER}-https.tls.certresolver=le diff --git a/overrides/compose.multi-bench.yaml b/overrides/compose.multi-bench.yaml index 7e681a18..0d9530e0 100644 --- a/overrides/compose.multi-bench.yaml +++ b/overrides/compose.multi-bench.yaml @@ -1,54 +1,54 @@ -services: - frontend: - networks: - - traefik-public - - bench-network - labels: - - traefik.enable=true - - traefik.docker.network=traefik-public - - traefik.http.services.${ROUTER?ROUTER not set}.loadbalancer.server.port=8080 - - traefik.http.routers.${ROUTER}-http.service=${ROUTER} - - traefik.http.routers.${ROUTER}-http.entrypoints=http - - traefik.http.routers.${ROUTER}-http.rule=Host(${SITES?SITES not set}) - configurator: - networks: - - bench-network - - mariadb-network - backend: - networks: - - mariadb-network - - bench-network - websocket: - networks: - - bench-network - - mariadb-network - scheduler: - networks: - - bench-network - - mariadb-network - queue-short: - networks: - - bench-network - - mariadb-network - queue-long: - networks: - - bench-network - - mariadb-network - redis-cache: - networks: - - bench-network - - mariadb-network - - redis-queue: - networks: - - bench-network - - mariadb-network - -networks: - traefik-public: - external: true - mariadb-network: - external: true - bench-network: - name: ${ROUTER} - external: false +services: + frontend: + networks: + - traefik-public + - bench-network + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.http.services.${ROUTER?ROUTER not set}.loadbalancer.server.port=8080 + - traefik.http.routers.${ROUTER}-http.service=${ROUTER} + - traefik.http.routers.${ROUTER}-http.entrypoints=http + - traefik.http.routers.${ROUTER}-http.rule=Host(${SITES?SITES not set}) + configurator: + networks: + - bench-network + - mariadb-network + backend: + networks: + - mariadb-network + - bench-network + websocket: + networks: + - bench-network + - mariadb-network + scheduler: + networks: + - bench-network + - mariadb-network + queue-short: + networks: + - bench-network + - mariadb-network + queue-long: + networks: + - bench-network + - mariadb-network + redis-cache: + networks: + - bench-network + - mariadb-network + + redis-queue: + networks: + - bench-network + - mariadb-network + +networks: + traefik-public: + external: true + mariadb-network: + external: true + bench-network: + name: ${ROUTER} + external: false diff --git a/overrides/compose.noproxy.yaml b/overrides/compose.noproxy.yaml index 23239ff2..ab1fc290 100644 --- a/overrides/compose.noproxy.yaml +++ b/overrides/compose.noproxy.yaml @@ -1,4 +1,4 @@ -services: - frontend: - ports: - - ${HTTP_PUBLISH_PORT:-8080}:8080 +services: + frontend: + ports: + - ${HTTP_PUBLISH_PORT:-8080}:8080 diff --git a/overrides/compose.postgres.yaml b/overrides/compose.postgres.yaml index 433ca71a..a9fbe657 100644 --- a/overrides/compose.postgres.yaml +++ b/overrides/compose.postgres.yaml @@ -1,18 +1,18 @@ -services: - configurator: - environment: - DB_HOST: db - DB_PORT: 5432 - depends_on: - - db - - db: - image: postgres:13.5 - command: [] - environment: - POSTGRES_PASSWORD: ${DB_PASSWORD:?No db password set} - volumes: - - db-data:/var/lib/postgresql/data - -volumes: - db-data: +services: + configurator: + environment: + DB_HOST: db + DB_PORT: 5432 + depends_on: + - db + + db: + image: postgres:13.5 + command: [] + environment: + POSTGRES_PASSWORD: ${DB_PASSWORD:?No db password set} + volumes: + - db-data:/var/lib/postgresql/data + +volumes: + db-data: diff --git a/overrides/compose.proxy.yaml b/overrides/compose.proxy.yaml index 32ce9fab..06ab34e9 100644 --- a/overrides/compose.proxy.yaml +++ b/overrides/compose.proxy.yaml @@ -1,19 +1,19 @@ -services: - frontend: - labels: - - traefik.enable=true - - traefik.http.services.frontend.loadbalancer.server.port=8080 - - traefik.http.routers.frontend-http.entrypoints=web - - traefik.http.routers.frontend-http.rule=HostRegexp(`{any:.+}`) - - proxy: - image: traefik:v2.11 - command: - - --providers.docker - - --providers.docker.exposedbydefault=false - - --entrypoints.web.address=:80 - ports: - - ${HTTP_PUBLISH_PORT:-80}:80 - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - userns_mode: host +services: + frontend: + labels: + - traefik.enable=true + - traefik.http.services.frontend.loadbalancer.server.port=8080 + - traefik.http.routers.frontend-http.entrypoints=web + - traefik.http.routers.frontend-http.rule=HostRegexp(`{any:.+}`) + + proxy: + image: traefik:v2.11 + command: + - --providers.docker + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + ports: + - ${HTTP_PUBLISH_PORT:-80}:80 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + userns_mode: host diff --git a/overrides/compose.redis.yaml b/overrides/compose.redis.yaml index 407ad9e1..fa0889de 100644 --- a/overrides/compose.redis.yaml +++ b/overrides/compose.redis.yaml @@ -1,21 +1,21 @@ -services: - configurator: - environment: - REDIS_CACHE: redis-cache:6379 - REDIS_QUEUE: redis-queue:6379 - depends_on: - - redis-cache - - redis-queue - - redis-cache: - image: redis:6.2-alpine - restart: unless-stopped - - redis-queue: - image: redis:6.2-alpine - restart: unless-stopped - volumes: - - redis-queue-data:/data - -volumes: - redis-queue-data: +services: + configurator: + environment: + REDIS_CACHE: redis-cache:6379 + REDIS_QUEUE: redis-queue:6379 + depends_on: + - redis-cache + - redis-queue + + redis-cache: + image: redis:6.2-alpine + restart: unless-stopped + + redis-queue: + image: redis:6.2-alpine + restart: unless-stopped + volumes: + - redis-queue-data:/data + +volumes: + redis-queue-data: diff --git a/overrides/compose.traefik-ssl.yaml b/overrides/compose.traefik-ssl.yaml index b83cb8e4..16f406fb 100644 --- a/overrides/compose.traefik-ssl.yaml +++ b/overrides/compose.traefik-ssl.yaml @@ -1,48 +1,48 @@ -services: - traefik: - labels: - # https-redirect middleware to redirect HTTP to HTTPS - # It can be reused by other stacks in other Docker Compose files - - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https - - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true - # traefik-http to use the middleware to redirect to https - - traefik.http.routers.traefik-public-http.middlewares=https-redirect - # traefik-https the actual router using HTTPS - # Uses the environment variable DOMAIN - - 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 - # Use the special Traefik service api@internal with the web UI/Dashboard - - traefik.http.routers.traefik-public-https.service=api@internal - # Use the "le" (Let's Encrypt) resolver created below - - traefik.http.routers.traefik-public-https.tls.certresolver=le - # Enable HTTP Basic auth, using the middleware created above - - traefik.http.routers.traefik-public-https.middlewares=admin-auth - command: - # Enable Docker in Traefik, so that it reads labels from Docker services - - --providers.docker=true - # Do not expose all Docker services, only the ones explicitly exposed - - --providers.docker.exposedbydefault=false - # Create an entrypoint http listening on port 80 - - --entrypoints.http.address=:80 - # Create an entrypoint https listening on port 443 - - --entrypoints.https.address=:443 - # Create the certificate resolver le for Let's Encrypt, uses the environment variable EMAIL - - --certificatesresolvers.le.acme.email=${EMAIL:?No EMAIL set} - # Store the Let's Encrypt certificates in the mounted volume - - --certificatesresolvers.le.acme.storage=/certificates/acme.json - # Use the TLS Challenge for Let's Encrypt - - --certificatesresolvers.le.acme.tlschallenge=true - # Enable the access log, with HTTP requests - - --accesslog - # Enable the Traefik log, for configurations and errors - - --log - # Enable the Dashboard and API - - --api - ports: - - ${HTTPS_PUBLISH_PORT:-443}:443 - volumes: - - cert-data:/certificates - -volumes: - cert-data: +services: + traefik: + labels: + # https-redirect middleware to redirect HTTP to HTTPS + # It can be reused by other stacks in other Docker Compose files + - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https + - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true + # traefik-http to use the middleware to redirect to https + - traefik.http.routers.traefik-public-http.middlewares=https-redirect + # traefik-https the actual router using HTTPS + # Uses the environment variable DOMAIN + - 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 + # Use the special Traefik service api@internal with the web UI/Dashboard + - traefik.http.routers.traefik-public-https.service=api@internal + # Use the "le" (Let's Encrypt) resolver created below + - traefik.http.routers.traefik-public-https.tls.certresolver=le + # Enable HTTP Basic auth, using the middleware created above + - traefik.http.routers.traefik-public-https.middlewares=admin-auth + command: + # Enable Docker in Traefik, so that it reads labels from Docker services + - --providers.docker=true + # Do not expose all Docker services, only the ones explicitly exposed + - --providers.docker.exposedbydefault=false + # Create an entrypoint http listening on port 80 + - --entrypoints.http.address=:80 + # Create an entrypoint https listening on port 443 + - --entrypoints.https.address=:443 + # Create the certificate resolver le for Let's Encrypt, uses the environment variable EMAIL + - --certificatesresolvers.le.acme.email=${EMAIL:?No EMAIL set} + # Store the Let's Encrypt certificates in the mounted volume + - --certificatesresolvers.le.acme.storage=/certificates/acme.json + # Use the TLS Challenge for Let's Encrypt + - --certificatesresolvers.le.acme.tlschallenge=true + # Enable the access log, with HTTP requests + - --accesslog + # Enable the Traefik log, for configurations and errors + - --log + # Enable the Dashboard and API + - --api + ports: + - ${HTTPS_PUBLISH_PORT:-443}:443 + volumes: + - cert-data:/certificates + +volumes: + cert-data: diff --git a/overrides/compose.traefik.yaml b/overrides/compose.traefik.yaml index 25d362af..a2096aca 100644 --- a/overrides/compose.traefik.yaml +++ b/overrides/compose.traefik.yaml @@ -1,47 +1,47 @@ -version: "3.3" - -services: - traefik: - image: "traefik:v2.11" - restart: unless-stopped - labels: - # Enable Traefik for this service, to make it available in the public network - - traefik.enable=true - # Use the traefik-public network (declared below) - - traefik.docker.network=traefik-public - # admin-auth middleware with HTTP Basic auth - # Using the environment variables USERNAME and HASHED_PASSWORD - - traefik.http.middlewares.admin-auth.basicauth.users=admin:${HASHED_PASSWORD:?No HASHED_PASSWORD set} - # Uses the environment variable TRAEFIK_DOMAIN - - traefik.http.routers.traefik-public-http.rule=Host(`${TRAEFIK_DOMAIN:?No TRAEFIK_DOMAIN set}`) - - traefik.http.routers.traefik-public-http.entrypoints=http - # Use the special Traefik service api@internal with the web UI/Dashboard - - traefik.http.routers.traefik-public-http.service=api@internal - # Enable HTTP Basic auth, using the middleware created above - - traefik.http.routers.traefik-public-http.middlewares=admin-auth - # Define the port inside of the Docker service to use - - traefik.http.services.traefik-public.loadbalancer.server.port=8080 - command: - # Enable Docker in Traefik, so that it reads labels from Docker services - - --providers.docker=true - # Do not expose all Docker services, only the ones explicitly exposed - - --providers.docker.exposedbydefault=false - # Create an entrypoint http listening on port 80 - - --entrypoints.http.address=:80 - # Enable the access log, with HTTP requests - - --accesslog - # Enable the Traefik log, for configurations and errors - - --log - # Enable the Dashboard and API - - --api - ports: - - ${HTTP_PUBLISH_PORT:-80}:80 - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - networks: - - traefik-public - -networks: - traefik-public: - name: traefik-public - external: false +version: "3.3" + +services: + traefik: + image: "traefik:v2.11" + restart: unless-stopped + labels: + # Enable Traefik for this service, to make it available in the public network + - traefik.enable=true + # Use the traefik-public network (declared below) + - traefik.docker.network=traefik-public + # admin-auth middleware with HTTP Basic auth + # Using the environment variables USERNAME and HASHED_PASSWORD + - traefik.http.middlewares.admin-auth.basicauth.users=admin:${HASHED_PASSWORD:?No HASHED_PASSWORD set} + # Uses the environment variable TRAEFIK_DOMAIN + - traefik.http.routers.traefik-public-http.rule=Host(`${TRAEFIK_DOMAIN:?No TRAEFIK_DOMAIN set}`) + - traefik.http.routers.traefik-public-http.entrypoints=http + # Use the special Traefik service api@internal with the web UI/Dashboard + - traefik.http.routers.traefik-public-http.service=api@internal + # Enable HTTP Basic auth, using the middleware created above + - traefik.http.routers.traefik-public-http.middlewares=admin-auth + # Define the port inside of the Docker service to use + - traefik.http.services.traefik-public.loadbalancer.server.port=8080 + command: + # Enable Docker in Traefik, so that it reads labels from Docker services + - --providers.docker=true + # Do not expose all Docker services, only the ones explicitly exposed + - --providers.docker.exposedbydefault=false + # Create an entrypoint http listening on port 80 + - --entrypoints.http.address=:80 + # Enable the access log, with HTTP requests + - --accesslog + # Enable the Traefik log, for configurations and errors + - --log + # Enable the Dashboard and API + - --api + ports: + - ${HTTP_PUBLISH_PORT:-80}:80 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - traefik-public + +networks: + traefik-public: + name: traefik-public + external: false diff --git a/pwd.yml b/pwd.yml index 7dfeae15..5971315c 100644 --- a/pwd.yml +++ b/pwd.yml @@ -1,225 +1,225 @@ -version: "3" - -services: - backend: - image: frappe/erpnext:v15.88.1 - networks: - - frappe_network - deploy: - restart_policy: - condition: on-failure - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - environment: - DB_HOST: db - DB_PORT: "3306" - MYSQL_ROOT_PASSWORD: admin - MARIADB_ROOT_PASSWORD: admin - - configurator: - image: frappe/erpnext:v15.88.1 - networks: - - frappe_network - deploy: - restart_policy: - condition: none - entrypoint: - - bash - - -c - command: - - > - ls -1 apps > sites/apps.txt; - bench set-config -g db_host $$DB_HOST; - bench set-config -gp db_port $$DB_PORT; - bench set-config -g redis_cache "redis://$$REDIS_CACHE"; - bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; - bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; - bench set-config -gp socketio_port $$SOCKETIO_PORT; - environment: - DB_HOST: db - DB_PORT: "3306" - REDIS_CACHE: redis-cache:6379 - REDIS_QUEUE: redis-queue:6379 - SOCKETIO_PORT: "9000" - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - create-site: - image: frappe/erpnext:v15.88.1 - networks: - - frappe_network - deploy: - restart_policy: - condition: none - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - entrypoint: - - bash - - -c - command: - - > - wait-for-it -t 120 db:3306; - wait-for-it -t 120 redis-cache:6379; - wait-for-it -t 120 redis-queue:6379; - export start=`date +%s`; - until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ - [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ - [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; - do - echo "Waiting for sites/common_site_config.json to be created"; - sleep 5; - if (( `date +%s`-start > 120 )); then - echo "could not find sites/common_site_config.json with required keys"; - exit 1 - fi - done; - echo "sites/common_site_config.json found"; - bench new-site --mariadb-user-host-login-scope='%' --admin-password=admin --db-root-username=root --db-root-password=admin --install-app erpnext --set-default frontend; - - db: - image: mariadb:10.6 - networks: - - frappe_network - healthcheck: - test: mysqladmin ping -h localhost --password=admin - interval: 1s - retries: 20 - deploy: - restart_policy: - condition: on-failure - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci - - --skip-character-set-client-handshake - - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 - environment: - MYSQL_ROOT_PASSWORD: admin - MARIADB_ROOT_PASSWORD: admin - volumes: - - db-data:/var/lib/mysql - - frontend: - image: frappe/erpnext:v15.88.1 - networks: - - frappe_network - depends_on: - - websocket - deploy: - restart_policy: - condition: on-failure - command: - - nginx-entrypoint.sh - environment: - BACKEND: backend:8000 - FRAPPE_SITE_NAME_HEADER: frontend - SOCKETIO: websocket:9000 - UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1 - UPSTREAM_REAL_IP_HEADER: X-Forwarded-For - UPSTREAM_REAL_IP_RECURSIVE: "off" - PROXY_READ_TIMEOUT: 120 - CLIENT_MAX_BODY_SIZE: 50m - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - ports: - - "8080:8080" - - queue-long: - image: frappe/erpnext:v15.88.1 - networks: - - frappe_network - deploy: - restart_policy: - condition: on-failure - command: - - bench - - worker - - --queue - - long,default,short - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - environment: - FRAPPE_REDIS_CACHE: redis://redis-cache:6379 - FRAPPE_REDIS_QUEUE: redis://redis-queue:6379 - - queue-short: - image: frappe/erpnext:v15.88.1 - networks: - - frappe_network - deploy: - restart_policy: - condition: on-failure - command: - - bench - - worker - - --queue - - short,default - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - environment: - FRAPPE_REDIS_CACHE: redis://redis-cache:6379 - FRAPPE_REDIS_QUEUE: redis://redis-queue:6379 - - redis-queue: - image: redis:6.2-alpine - networks: - - frappe_network - deploy: - restart_policy: - condition: on-failure - volumes: - - redis-queue-data:/data - - redis-cache: - image: redis:6.2-alpine - networks: - - frappe_network - deploy: - restart_policy: - condition: on-failure - - scheduler: - image: frappe/erpnext:v15.88.1 - networks: - - frappe_network - deploy: - restart_policy: - condition: on-failure - command: - - bench - - schedule - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - - websocket: - image: frappe/erpnext:v15.88.1 - networks: - - frappe_network - deploy: - restart_policy: - condition: on-failure - command: - - node - - /home/frappe/frappe-bench/apps/frappe/socketio.js - environment: - FRAPPE_REDIS_CACHE: redis://redis-cache:6379 - FRAPPE_REDIS_QUEUE: redis://redis-queue:6379 - volumes: - - sites:/home/frappe/frappe-bench/sites - - logs:/home/frappe/frappe-bench/logs - -volumes: - db-data: - redis-queue-data: - sites: - logs: - -networks: - frappe_network: - driver: bridge +version: "3" + +services: + backend: + image: frappe/erpnext:v15.88.1 + networks: + - frappe_network + deploy: + restart_policy: + condition: on-failure + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + environment: + DB_HOST: db + DB_PORT: "3306" + MYSQL_ROOT_PASSWORD: admin + MARIADB_ROOT_PASSWORD: admin + + configurator: + image: frappe/erpnext:v15.88.1 + networks: + - frappe_network + deploy: + restart_policy: + condition: none + entrypoint: + - bash + - -c + command: + - > + ls -1 apps > sites/apps.txt; + bench set-config -g db_host $$DB_HOST; + bench set-config -gp db_port $$DB_PORT; + bench set-config -g redis_cache "redis://$$REDIS_CACHE"; + bench set-config -g redis_queue "redis://$$REDIS_QUEUE"; + bench set-config -g redis_socketio "redis://$$REDIS_QUEUE"; + bench set-config -gp socketio_port $$SOCKETIO_PORT; + environment: + DB_HOST: db + DB_PORT: "3306" + REDIS_CACHE: redis-cache:6379 + REDIS_QUEUE: redis-queue:6379 + SOCKETIO_PORT: "9000" + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + create-site: + image: frappe/erpnext:v15.88.1 + networks: + - frappe_network + deploy: + restart_policy: + condition: none + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + entrypoint: + - bash + - -c + command: + - > + wait-for-it -t 120 db:3306; + wait-for-it -t 120 redis-cache:6379; + wait-for-it -t 120 redis-queue:6379; + export start=`date +%s`; + until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \ + [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]]; + do + echo "Waiting for sites/common_site_config.json to be created"; + sleep 5; + if (( `date +%s`-start > 120 )); then + echo "could not find sites/common_site_config.json with required keys"; + exit 1 + fi + done; + echo "sites/common_site_config.json found"; + bench new-site --mariadb-user-host-login-scope='%' --admin-password=admin --db-root-username=root --db-root-password=admin --install-app erpnext --set-default frontend; + + db: + image: mariadb:10.6 + networks: + - frappe_network + healthcheck: + test: mysqladmin ping -h localhost --password=admin + interval: 1s + retries: 20 + deploy: + restart_policy: + condition: on-failure + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --skip-character-set-client-handshake + - --skip-innodb-read-only-compressed # Temporary fix for MariaDB 10.6 + environment: + MYSQL_ROOT_PASSWORD: admin + MARIADB_ROOT_PASSWORD: admin + volumes: + - db-data:/var/lib/mysql + + frontend: + image: frappe/erpnext:v15.88.1 + networks: + - frappe_network + depends_on: + - websocket + deploy: + restart_policy: + condition: on-failure + command: + - nginx-entrypoint.sh + environment: + BACKEND: backend:8000 + FRAPPE_SITE_NAME_HEADER: frontend + SOCKETIO: websocket:9000 + UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1 + UPSTREAM_REAL_IP_HEADER: X-Forwarded-For + UPSTREAM_REAL_IP_RECURSIVE: "off" + PROXY_READ_TIMEOUT: 120 + CLIENT_MAX_BODY_SIZE: 50m + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + ports: + - "8080:8080" + + queue-long: + image: frappe/erpnext:v15.88.1 + networks: + - frappe_network + deploy: + restart_policy: + condition: on-failure + command: + - bench + - worker + - --queue + - long,default,short + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + environment: + FRAPPE_REDIS_CACHE: redis://redis-cache:6379 + FRAPPE_REDIS_QUEUE: redis://redis-queue:6379 + + queue-short: + image: frappe/erpnext:v15.88.1 + networks: + - frappe_network + deploy: + restart_policy: + condition: on-failure + command: + - bench + - worker + - --queue + - short,default + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + environment: + FRAPPE_REDIS_CACHE: redis://redis-cache:6379 + FRAPPE_REDIS_QUEUE: redis://redis-queue:6379 + + redis-queue: + image: redis:6.2-alpine + networks: + - frappe_network + deploy: + restart_policy: + condition: on-failure + volumes: + - redis-queue-data:/data + + redis-cache: + image: redis:6.2-alpine + networks: + - frappe_network + deploy: + restart_policy: + condition: on-failure + + scheduler: + image: frappe/erpnext:v15.88.1 + networks: + - frappe_network + deploy: + restart_policy: + condition: on-failure + command: + - bench + - schedule + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + + websocket: + image: frappe/erpnext:v15.88.1 + networks: + - frappe_network + deploy: + restart_policy: + condition: on-failure + command: + - node + - /home/frappe/frappe-bench/apps/frappe/socketio.js + environment: + FRAPPE_REDIS_CACHE: redis://redis-cache:6379 + FRAPPE_REDIS_QUEUE: redis://redis-queue:6379 + volumes: + - sites:/home/frappe/frappe-bench/sites + - logs:/home/frappe/frappe-bench/logs + +volumes: + db-data: + redis-queue-data: + sites: + logs: + +networks: + frappe_network: + driver: bridge diff --git a/requirements-test.txt b/requirements-test.txt index 9471b3d9..ef9805b5 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1 +1 @@ -pytest==8.4.2 +pytest==8.4.2 diff --git a/resources/nginx-entrypoint.sh b/resources/nginx-entrypoint.sh index 50408e56..4195fc82 100755 --- a/resources/nginx-entrypoint.sh +++ b/resources/nginx-entrypoint.sh @@ -1,52 +1,52 @@ -#!/bin/bash - -# Set variables that do not exist -if [[ -z "$BACKEND" ]]; then - echo "BACKEND defaulting to 0.0.0.0:8000" - export BACKEND=0.0.0.0:8000 -fi -if [[ -z "$SOCKETIO" ]]; then - echo "SOCKETIO defaulting to 0.0.0.0:9000" - export SOCKETIO=0.0.0.0:9000 -fi -if [[ -z "$UPSTREAM_REAL_IP_ADDRESS" ]]; then - echo "UPSTREAM_REAL_IP_ADDRESS defaulting to 127.0.0.1" - export UPSTREAM_REAL_IP_ADDRESS=127.0.0.1 -fi -if [[ -z "$UPSTREAM_REAL_IP_HEADER" ]]; then - echo "UPSTREAM_REAL_IP_HEADER defaulting to X-Forwarded-For" - export UPSTREAM_REAL_IP_HEADER=X-Forwarded-For -fi -if [[ -z "$UPSTREAM_REAL_IP_RECURSIVE" ]]; then - echo "UPSTREAM_REAL_IP_RECURSIVE defaulting to off" - export UPSTREAM_REAL_IP_RECURSIVE=off -fi -if [[ -z "$FRAPPE_SITE_NAME_HEADER" ]]; then - # shellcheck disable=SC2016 - echo 'FRAPPE_SITE_NAME_HEADER defaulting to $host' - # shellcheck disable=SC2016 - export FRAPPE_SITE_NAME_HEADER='$host' -fi - -if [[ -z "$PROXY_READ_TIMEOUT" ]]; then - echo "PROXY_READ_TIMEOUT defaulting to 120" - export PROXY_READ_TIMEOUT=120 -fi - -if [[ -z "$CLIENT_MAX_BODY_SIZE" ]]; then - echo "CLIENT_MAX_BODY_SIZE defaulting to 50m" - export CLIENT_MAX_BODY_SIZE=50m -fi - -# shellcheck disable=SC2016 -envsubst '${BACKEND} - ${SOCKETIO} - ${UPSTREAM_REAL_IP_ADDRESS} - ${UPSTREAM_REAL_IP_HEADER} - ${UPSTREAM_REAL_IP_RECURSIVE} - ${FRAPPE_SITE_NAME_HEADER} - ${PROXY_READ_TIMEOUT} - ${CLIENT_MAX_BODY_SIZE}' \ - /etc/nginx/conf.d/frappe.conf - -nginx -g 'daemon off;' +#!/bin/bash + +# Set variables that do not exist +if [[ -z "$BACKEND" ]]; then + echo "BACKEND defaulting to 0.0.0.0:8000" + export BACKEND=0.0.0.0:8000 +fi +if [[ -z "$SOCKETIO" ]]; then + echo "SOCKETIO defaulting to 0.0.0.0:9000" + export SOCKETIO=0.0.0.0:9000 +fi +if [[ -z "$UPSTREAM_REAL_IP_ADDRESS" ]]; then + echo "UPSTREAM_REAL_IP_ADDRESS defaulting to 127.0.0.1" + export UPSTREAM_REAL_IP_ADDRESS=127.0.0.1 +fi +if [[ -z "$UPSTREAM_REAL_IP_HEADER" ]]; then + echo "UPSTREAM_REAL_IP_HEADER defaulting to X-Forwarded-For" + export UPSTREAM_REAL_IP_HEADER=X-Forwarded-For +fi +if [[ -z "$UPSTREAM_REAL_IP_RECURSIVE" ]]; then + echo "UPSTREAM_REAL_IP_RECURSIVE defaulting to off" + export UPSTREAM_REAL_IP_RECURSIVE=off +fi +if [[ -z "$FRAPPE_SITE_NAME_HEADER" ]]; then + # shellcheck disable=SC2016 + echo 'FRAPPE_SITE_NAME_HEADER defaulting to $host' + # shellcheck disable=SC2016 + export FRAPPE_SITE_NAME_HEADER='$host' +fi + +if [[ -z "$PROXY_READ_TIMEOUT" ]]; then + echo "PROXY_READ_TIMEOUT defaulting to 120" + export PROXY_READ_TIMEOUT=120 +fi + +if [[ -z "$CLIENT_MAX_BODY_SIZE" ]]; then + echo "CLIENT_MAX_BODY_SIZE defaulting to 50m" + export CLIENT_MAX_BODY_SIZE=50m +fi + +# shellcheck disable=SC2016 +envsubst '${BACKEND} + ${SOCKETIO} + ${UPSTREAM_REAL_IP_ADDRESS} + ${UPSTREAM_REAL_IP_HEADER} + ${UPSTREAM_REAL_IP_RECURSIVE} + ${FRAPPE_SITE_NAME_HEADER} + ${PROXY_READ_TIMEOUT} + ${CLIENT_MAX_BODY_SIZE}' \ + /etc/nginx/conf.d/frappe.conf + +nginx -g 'daemon off;' diff --git a/resources/nginx-template.conf b/resources/nginx-template.conf index ded97c94..e5bbc36d 100644 --- a/resources/nginx-template.conf +++ b/resources/nginx-template.conf @@ -1,107 +1,107 @@ -upstream backend-server { - server ${BACKEND} fail_timeout=0; -} - -upstream socketio-server { - server ${SOCKETIO} fail_timeout=0; -} - -server { - listen 8080; - server_name ${FRAPPE_SITE_NAME_HEADER}; - root /home/frappe/frappe-bench/sites; - - proxy_buffer_size 128k; - proxy_buffers 4 256k; - proxy_busy_buffers_size 256k; - - add_header X-Frame-Options "SAMEORIGIN"; - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - add_header Referrer-Policy "same-origin, strict-origin-when-cross-origin"; - - set_real_ip_from ${UPSTREAM_REAL_IP_ADDRESS}; - real_ip_header ${UPSTREAM_REAL_IP_HEADER}; - real_ip_recursive ${UPSTREAM_REAL_IP_RECURSIVE}; - - location /assets { - try_files $uri =404; - } - - location ~ ^/protected/(.*) { - internal; - try_files /${FRAPPE_SITE_NAME_HEADER}/$1 =404; - } - - location /socket.io { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER}; - proxy_set_header Origin $scheme://$http_host; - proxy_set_header Host $host; - - proxy_pass http://socketio-server; - } - - location / { - rewrite ^(.+)/$ $1 permanent; - rewrite ^(.+)/index\.html$ $1 permanent; - rewrite ^(.+)\.html$ $1 permanent; - - location ~ ^/files/.*.(htm|html|svg|xml) { - add_header Content-disposition "attachment"; - try_files /${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver; - } - - try_files /${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver; - } - - location @webserver { - proxy_http_version 1.1; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER}; - proxy_set_header Host $host; - proxy_set_header X-Use-X-Accel-Redirect True; - proxy_read_timeout ${PROXY_READ_TIMEOUT}; - proxy_redirect off; - - proxy_pass http://backend-server; - } - - # optimizations - sendfile on; - keepalive_timeout 15; - client_max_body_size ${CLIENT_MAX_BODY_SIZE}; - client_body_buffer_size 16K; - client_header_buffer_size 1k; - - # enable gzip compression - # based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge - gzip on; - gzip_http_version 1.1; - gzip_comp_level 5; - gzip_min_length 256; - gzip_proxied any; - gzip_vary on; - gzip_types - application/atom+xml - application/javascript - application/json - application/rss+xml - application/vnd.ms-fontobject - application/x-font-ttf - application/font-woff - application/x-web-app-manifest+json - application/xhtml+xml - application/xml - font/opentype - image/svg+xml - image/x-icon - text/css - text/plain - text/x-component; - # text/html is always compressed by HttpGzipModule -} +upstream backend-server { + server ${BACKEND} fail_timeout=0; +} + +upstream socketio-server { + server ${SOCKETIO} fail_timeout=0; +} + +server { + listen 8080; + server_name ${FRAPPE_SITE_NAME_HEADER}; + root /home/frappe/frappe-bench/sites; + + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + + add_header X-Frame-Options "SAMEORIGIN"; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header Referrer-Policy "same-origin, strict-origin-when-cross-origin"; + + set_real_ip_from ${UPSTREAM_REAL_IP_ADDRESS}; + real_ip_header ${UPSTREAM_REAL_IP_HEADER}; + real_ip_recursive ${UPSTREAM_REAL_IP_RECURSIVE}; + + location /assets { + try_files $uri =404; + } + + location ~ ^/protected/(.*) { + internal; + try_files /${FRAPPE_SITE_NAME_HEADER}/$1 =404; + } + + location /socket.io { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER}; + proxy_set_header Origin $scheme://$http_host; + proxy_set_header Host $host; + + proxy_pass http://socketio-server; + } + + location / { + rewrite ^(.+)/$ $1 permanent; + rewrite ^(.+)/index\.html$ $1 permanent; + rewrite ^(.+)\.html$ $1 permanent; + + location ~ ^/files/.*.(htm|html|svg|xml) { + add_header Content-disposition "attachment"; + try_files /${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver; + } + + try_files /${FRAPPE_SITE_NAME_HEADER}/public/$uri @webserver; + } + + location @webserver { + proxy_http_version 1.1; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frappe-Site-Name ${FRAPPE_SITE_NAME_HEADER}; + proxy_set_header Host $host; + proxy_set_header X-Use-X-Accel-Redirect True; + proxy_read_timeout ${PROXY_READ_TIMEOUT}; + proxy_redirect off; + + proxy_pass http://backend-server; + } + + # optimizations + sendfile on; + keepalive_timeout 15; + client_max_body_size ${CLIENT_MAX_BODY_SIZE}; + client_body_buffer_size 16K; + client_header_buffer_size 1k; + + # enable gzip compression + # based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge + gzip on; + gzip_http_version 1.1; + gzip_comp_level 5; + gzip_min_length 256; + gzip_proxied any; + gzip_vary on; + gzip_types + application/atom+xml + application/javascript + application/json + application/rss+xml + application/vnd.ms-fontobject + application/x-font-ttf + application/font-woff + application/x-web-app-manifest+json + application/xhtml+xml + application/xml + font/opentype + image/svg+xml + image/x-icon + text/css + text/plain + text/x-component; + # text/html is always compressed by HttpGzipModule +} diff --git a/setup.cfg b/setup.cfg index ae4ce36a..e5fec751 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,12 +1,12 @@ -# Config file for isort, codespell and other Python projects. -# In this case it is not used for packaging. - -[isort] -profile = black -known_third_party = frappe - -[codespell] -skip = images/bench/Dockerfile - -[tool:pytest] -addopts = -s --exitfirst +# Config file for isort, codespell and other Python projects. +# In this case it is not used for packaging. + +[isort] +profile = black +known_third_party = frappe + +[codespell] +skip = images/bench/Dockerfile + +[tool:pytest] +addopts = -s --exitfirst diff --git a/tests/_check_connections.py b/tests/_check_connections.py index 85ce87fc..ea7dab31 100644 --- a/tests/_check_connections.py +++ b/tests/_check_connections.py @@ -1,52 +1,52 @@ -from __future__ import annotations - -import asyncio -import json -import socket -from typing import Any, Iterable, Tuple - -Address = Tuple[str, int] - - -async def wait_for_port(address: Address) -> None: - # From https://github.com/clarketm/wait-for-it - while True: - try: - _, writer = await asyncio.open_connection(*address) - writer.close() - await writer.wait_closed() - break - except (socket.gaierror, ConnectionError, OSError, TypeError): - pass - await asyncio.sleep(0.1) - - -def get_redis_url(addr: str) -> Address: - result = addr.replace("redis://", "") - result = result.split("/")[0] - parts = result.split(":") - assert len(parts) == 2 - return parts[0], int(parts[1]) - - -def get_addresses(config: dict[str, Any]) -> Iterable[Address]: - yield (config["db_host"], config["db_port"]) - for key in ("redis_cache", "redis_queue"): - yield get_redis_url(config[key]) - - -async def async_main(addresses: set[Address]) -> None: - tasks = [asyncio.wait_for(wait_for_port(addr), timeout=5) for addr in addresses] - await asyncio.gather(*tasks) - - -def main() -> int: - with open("/home/frappe/frappe-bench/sites/common_site_config.json") as f: - config = json.load(f) - addresses = set(get_addresses(config)) - asyncio.run(async_main(addresses)) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) +from __future__ import annotations + +import asyncio +import json +import socket +from typing import Any, Iterable, Tuple + +Address = Tuple[str, int] + + +async def wait_for_port(address: Address) -> None: + # From https://github.com/clarketm/wait-for-it + while True: + try: + _, writer = await asyncio.open_connection(*address) + writer.close() + await writer.wait_closed() + break + except (socket.gaierror, ConnectionError, OSError, TypeError): + pass + await asyncio.sleep(0.1) + + +def get_redis_url(addr: str) -> Address: + result = addr.replace("redis://", "") + result = result.split("/")[0] + parts = result.split(":") + assert len(parts) == 2 + return parts[0], int(parts[1]) + + +def get_addresses(config: dict[str, Any]) -> Iterable[Address]: + yield (config["db_host"], config["db_port"]) + for key in ("redis_cache", "redis_queue"): + yield get_redis_url(config[key]) + + +async def async_main(addresses: set[Address]) -> None: + tasks = [asyncio.wait_for(wait_for_port(addr), timeout=5) for addr in addresses] + await asyncio.gather(*tasks) + + +def main() -> int: + with open("/home/frappe/frappe-bench/sites/common_site_config.json") as f: + config = json.load(f) + addresses = set(get_addresses(config)) + asyncio.run(async_main(addresses)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/_check_website_theme.py b/tests/_check_website_theme.py index bc7ca8f4..6db9257c 100644 --- a/tests/_check_website_theme.py +++ b/tests/_check_website_theme.py @@ -1,17 +1,17 @@ -import frappe - - -def check_website_theme(): - doc = frappe.new_doc("Website Theme") - doc.theme = "test theme" - doc.insert() - - -def main() -> int: - frappe.connect(site="tests") - check_website_theme() - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) +import frappe + + +def check_website_theme(): + doc = frappe.new_doc("Website Theme") + doc.theme = "test theme" + doc.insert() + + +def main() -> int: + frappe.connect(site="tests") + check_website_theme() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/_create_bucket.py b/tests/_create_bucket.py index e0b55cbf..d31a4844 100644 --- a/tests/_create_bucket.py +++ b/tests/_create_bucket.py @@ -1,19 +1,19 @@ -import os - -import boto3 - - -def main() -> int: - resource = boto3.resource( - service_name="s3", - endpoint_url="http://minio:9000", - region_name="us-east-1", - aws_access_key_id=os.getenv("S3_ACCESS_KEY"), - aws_secret_access_key=os.getenv("S3_SECRET_KEY"), - ) - resource.create_bucket(Bucket="frappe") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) +import os + +import boto3 + + +def main() -> int: + resource = boto3.resource( + service_name="s3", + endpoint_url="http://minio:9000", + region_name="us-east-1", + aws_access_key_id=os.getenv("S3_ACCESS_KEY"), + aws_secret_access_key=os.getenv("S3_SECRET_KEY"), + ) + resource.create_bucket(Bucket="frappe") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/_ping_frappe_connections.py b/tests/_ping_frappe_connections.py index 80aae105..f7bc2a08 100644 --- a/tests/_ping_frappe_connections.py +++ b/tests/_ping_frappe_connections.py @@ -1,26 +1,26 @@ -import frappe - - -def check_db(): - doc = frappe.get_single("System Settings") - assert any(v is None for v in doc.as_dict().values()), "Database test didn't pass" - print("Database works!") - - -def check_cache(): - key_and_name = "mytestkey", "mytestname" - frappe.cache().hset(*key_and_name, "mytestvalue") - assert frappe.cache().hget(*key_and_name) == "mytestvalue", "Cache test didn't pass" - frappe.cache().hdel(*key_and_name) - print("Cache works!") - - -def main() -> int: - frappe.connect(site="tests.localhost") - check_db() - check_cache() - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) +import frappe + + +def check_db(): + doc = frappe.get_single("System Settings") + assert any(v is None for v in doc.as_dict().values()), "Database test didn't pass" + print("Database works!") + + +def check_cache(): + key_and_name = "mytestkey", "mytestname" + frappe.cache().hset(*key_and_name, "mytestvalue") + assert frappe.cache().hget(*key_and_name) == "mytestvalue", "Cache test didn't pass" + frappe.cache().hdel(*key_and_name) + print("Cache works!") + + +def main() -> int: + frappe.connect(site="tests.localhost") + check_db() + check_cache() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/compose.ci.yaml b/tests/compose.ci.yaml index 81c952a5..471ee85c 100644 --- a/tests/compose.ci.yaml +++ b/tests/compose.ci.yaml @@ -1,21 +1,21 @@ -services: - configurator: - image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} - - backend: - image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} - - frontend: - image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} - - websocket: - image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} - - queue-short: - image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} - - queue-long: - image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} - - scheduler: - image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} +services: + configurator: + image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} + + backend: + image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} + + frontend: + image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} + + websocket: + image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} + + queue-short: + image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} + + queue-long: + image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} + + scheduler: + image: localhost:5000/frappe/erpnext:${ERPNEXT_VERSION} diff --git a/tests/conftest.py b/tests/conftest.py index c6ff166a..c1149aeb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,166 +1,166 @@ -import os -import re -import shutil -import subprocess -from dataclasses import dataclass -from pathlib import Path - -import pytest - -from tests.utils import CI, Compose - - -def _add_version_var(name: str, env_path: Path): - value = os.getenv(name) - - if not value: - return - - if value == "develop": - os.environ[name] = "latest" - - with open(env_path, "a") as f: - f.write(f"\n{name}={os.environ[name]}") - - -def _add_sites_var(env_path: Path): - with open(env_path, "r+") as f: - content = f.read() - content = re.sub( - rf"SITES=.*", - f"SITES=`tests.localhost`,`test-erpnext-site.localhost`,`test-pg-site.localhost`", - content, - ) - f.seek(0) - f.truncate() - f.write(content) - - -@pytest.fixture(scope="session") -def env_file(tmp_path_factory: pytest.TempPathFactory): - tmp_path = tmp_path_factory.mktemp("frappe-docker") - file_path = tmp_path / ".env" - shutil.copy("example.env", file_path) - - _add_sites_var(file_path) - - for var in ("FRAPPE_VERSION", "ERPNEXT_VERSION"): - _add_version_var(name=var, env_path=file_path) - - yield str(file_path) - os.remove(file_path) - - -@pytest.fixture(scope="session") -def compose(env_file: str): - return Compose(project_name="test", env_file=env_file) - - -@pytest.fixture(autouse=True, scope="session") -def frappe_setup(compose: Compose): - compose.stop() - - compose("up", "-d", "--quiet-pull") - yield - - compose.stop() - - -@pytest.fixture(scope="session") -def frappe_site(compose: Compose): - site_name = "tests.localhost" - compose.bench( - "new-site", - # TODO: change to --mariadb-user-host-login-scope=% - "--no-mariadb-socket", - "--db-root-password=123", - "--admin-password=admin", - site_name, - ) - compose("restart", "backend") - yield site_name - - -@pytest.fixture(scope="class") -def erpnext_setup(compose: Compose): - compose.stop() - compose("up", "-d", "--quiet-pull") - - yield - compose.stop() - - -@pytest.fixture(scope="class") -def erpnext_site(compose: Compose): - site_name = "test-erpnext-site.localhost" - args = [ - "new-site", - # TODO: change to --mariadb-user-host-login-scope=% - "--no-mariadb-socket", - "--db-root-password=123", - "--admin-password=admin", - "--install-app=erpnext", - site_name, - ] - compose.bench(*args) - compose("restart", "backend") - yield site_name - - -@pytest.fixture -def postgres_setup(compose: Compose): - compose.stop() - compose("-f", "overrides/compose.postgres.yaml", "up", "-d", "--quiet-pull") - compose.bench("set-config", "-g", "root_login", "postgres") - compose.bench("set-config", "-g", "root_password", "123") - yield - compose.stop() - - -@pytest.fixture -def python_path(): - return "/home/frappe/frappe-bench/env/bin/python" - - -@dataclass -class S3ServiceResult: - access_key: str - secret_key: str - - -@pytest.fixture -def s3_service(python_path: str, compose: Compose): - access_key = "AKIAIOSFODNN7EXAMPLE" - secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" - cmd = ( - "docker", - "run", - "--name", - "minio", - "-d", - "-e", - f"MINIO_ACCESS_KEY={access_key}", - "-e", - f"MINIO_SECRET_KEY={secret_key}", - "--network", - f"{compose.project_name}_default", - "minio/minio", - "server", - "/data", - ) - subprocess.check_call(cmd) - - compose("cp", "tests/_create_bucket.py", "backend:/tmp") - compose.exec("backend", "bench", "pip", "install", "boto3~=1.34.143") - compose.exec( - "-e", - f"S3_ACCESS_KEY={access_key}", - "-e", - f"S3_SECRET_KEY={secret_key}", - "backend", - python_path, - "/tmp/_create_bucket.py", - ) - - yield S3ServiceResult(access_key=access_key, secret_key=secret_key) - subprocess.call(("docker", "rm", "minio", "-f")) +import os +import re +import shutil +import subprocess +from dataclasses import dataclass +from pathlib import Path + +import pytest + +from tests.utils import CI, Compose + + +def _add_version_var(name: str, env_path: Path): + value = os.getenv(name) + + if not value: + return + + if value == "develop": + os.environ[name] = "latest" + + with open(env_path, "a") as f: + f.write(f"\n{name}={os.environ[name]}") + + +def _add_sites_var(env_path: Path): + with open(env_path, "r+") as f: + content = f.read() + content = re.sub( + rf"SITES=.*", + f"SITES=`tests.localhost`,`test-erpnext-site.localhost`,`test-pg-site.localhost`", + content, + ) + f.seek(0) + f.truncate() + f.write(content) + + +@pytest.fixture(scope="session") +def env_file(tmp_path_factory: pytest.TempPathFactory): + tmp_path = tmp_path_factory.mktemp("frappe-docker") + file_path = tmp_path / ".env" + shutil.copy("example.env", file_path) + + _add_sites_var(file_path) + + for var in ("FRAPPE_VERSION", "ERPNEXT_VERSION"): + _add_version_var(name=var, env_path=file_path) + + yield str(file_path) + os.remove(file_path) + + +@pytest.fixture(scope="session") +def compose(env_file: str): + return Compose(project_name="test", env_file=env_file) + + +@pytest.fixture(autouse=True, scope="session") +def frappe_setup(compose: Compose): + compose.stop() + + compose("up", "-d", "--quiet-pull") + yield + + compose.stop() + + +@pytest.fixture(scope="session") +def frappe_site(compose: Compose): + site_name = "tests.localhost" + compose.bench( + "new-site", + # TODO: change to --mariadb-user-host-login-scope=% + "--no-mariadb-socket", + "--db-root-password=123", + "--admin-password=admin", + site_name, + ) + compose("restart", "backend") + yield site_name + + +@pytest.fixture(scope="class") +def erpnext_setup(compose: Compose): + compose.stop() + compose("up", "-d", "--quiet-pull") + + yield + compose.stop() + + +@pytest.fixture(scope="class") +def erpnext_site(compose: Compose): + site_name = "test-erpnext-site.localhost" + args = [ + "new-site", + # TODO: change to --mariadb-user-host-login-scope=% + "--no-mariadb-socket", + "--db-root-password=123", + "--admin-password=admin", + "--install-app=erpnext", + site_name, + ] + compose.bench(*args) + compose("restart", "backend") + yield site_name + + +@pytest.fixture +def postgres_setup(compose: Compose): + compose.stop() + compose("-f", "overrides/compose.postgres.yaml", "up", "-d", "--quiet-pull") + compose.bench("set-config", "-g", "root_login", "postgres") + compose.bench("set-config", "-g", "root_password", "123") + yield + compose.stop() + + +@pytest.fixture +def python_path(): + return "/home/frappe/frappe-bench/env/bin/python" + + +@dataclass +class S3ServiceResult: + access_key: str + secret_key: str + + +@pytest.fixture +def s3_service(python_path: str, compose: Compose): + access_key = "AKIAIOSFODNN7EXAMPLE" + secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + cmd = ( + "docker", + "run", + "--name", + "minio", + "-d", + "-e", + f"MINIO_ACCESS_KEY={access_key}", + "-e", + f"MINIO_SECRET_KEY={secret_key}", + "--network", + f"{compose.project_name}_default", + "minio/minio", + "server", + "/data", + ) + subprocess.check_call(cmd) + + compose("cp", "tests/_create_bucket.py", "backend:/tmp") + compose.exec("backend", "bench", "pip", "install", "boto3~=1.34.143") + compose.exec( + "-e", + f"S3_ACCESS_KEY={access_key}", + "-e", + f"S3_SECRET_KEY={secret_key}", + "backend", + python_path, + "/tmp/_create_bucket.py", + ) + + yield S3ServiceResult(access_key=access_key, secret_key=secret_key) + subprocess.call(("docker", "rm", "minio", "-f")) diff --git a/tests/test_frappe_docker.py b/tests/test_frappe_docker.py index 85e6f1e8..eb7366d8 100644 --- a/tests/test_frappe_docker.py +++ b/tests/test_frappe_docker.py @@ -1,151 +1,151 @@ -import os -from pathlib import Path -from typing import Any - -import pytest - -from tests.conftest import S3ServiceResult -from tests.utils import Compose, check_url_content - -BACKEND_SERVICES = ( - "backend", - "queue-short", - "queue-long", - "scheduler", -) - - -@pytest.mark.parametrize("service", BACKEND_SERVICES) -def test_links_in_backends(service: str, compose: Compose, python_path: str): - filename = "_check_connections.py" - compose("cp", f"tests/{filename}", f"{service}:/tmp/") - compose.exec(service, python_path, f"/tmp/{filename}") - - -def index_cb(text: str): - if "404 page not found" not in text: - return text[:200] - - -def api_cb(text: str): - if '"message"' in text: - return text - - -def assets_cb(text: str): - if text: - return text[:200] - - -@pytest.mark.parametrize( - ("url", "callback"), (("/", index_cb), ("/api/method/ping", api_cb)) -) -def test_endpoints(url: str, callback: Any, frappe_site: str): - check_url_content( - url=f"http://127.0.0.1{url}", callback=callback, site_name=frappe_site - ) - - -@pytest.mark.skipif( - os.environ["FRAPPE_VERSION"][0:3] == "v12", reason="v12 doesn't have the asset" -) -def test_assets_endpoint(frappe_site: str): - check_url_content( - url=f"http://127.0.0.1/assets/frappe/images/frappe-framework-logo.svg", - callback=assets_cb, - site_name=frappe_site, - ) - - -def test_files_reachable(frappe_site: str, tmp_path: Path, compose: Compose): - content = "lalala\n" - file_path = tmp_path / "testfile.txt" - - with file_path.open("w") as f: - f.write(content) - - compose( - "cp", - str(file_path), - f"backend:/home/frappe/frappe-bench/sites/{frappe_site}/public/files/", - ) - - def callback(text: str): - if text == content: - return text - - check_url_content( - url=f"http://127.0.0.1/files/{file_path.name}", - callback=callback, - site_name=frappe_site, - ) - - -@pytest.mark.parametrize("service", BACKEND_SERVICES) -@pytest.mark.usefixtures("frappe_site") -def test_frappe_connections_in_backends( - service: str, python_path: str, compose: Compose -): - filename = "_ping_frappe_connections.py" - compose("cp", f"tests/{filename}", f"{service}:/tmp/") - compose.exec( - "-w", - "/home/frappe/frappe-bench/sites", - service, - python_path, - f"/tmp/{filename}", - ) - - -def test_push_backup( - frappe_site: str, - s3_service: S3ServiceResult, - compose: Compose, -): - restic_password = "secret" - compose.bench("--site", frappe_site, "backup", "--with-files") - restic_args = [ - "--env=RESTIC_REPOSITORY=s3:http://minio:9000/frappe", - f"--env=AWS_ACCESS_KEY_ID={s3_service.access_key}", - f"--env=AWS_SECRET_ACCESS_KEY={s3_service.secret_key}", - f"--env=RESTIC_PASSWORD={restic_password}", - ] - compose.exec(*restic_args, "backend", "restic", "init") - compose.exec(*restic_args, "backend", "restic", "backup", "sites") - compose.exec(*restic_args, "backend", "restic", "snapshots") - - -def test_https(frappe_site: str, compose: Compose): - compose("-f", "overrides/compose.https.yaml", "up", "-d") - check_url_content(url="https://127.0.0.1", callback=index_cb, site_name=frappe_site) - - -@pytest.mark.usefixtures("erpnext_setup") -class TestErpnext: - @pytest.mark.parametrize( - ("url", "callback"), - ( - ( - "/api/method/erpnext.templates.pages.search_help.get_help_results_sections?text=help", - api_cb, - ), - ("/assets/erpnext/js/setup_wizard.js", assets_cb), - ), - ) - def test_endpoints(self, url: str, callback: Any, erpnext_site: str): - check_url_content( - url=f"http://127.0.0.1{url}", callback=callback, site_name=erpnext_site - ) - - -@pytest.mark.usefixtures("postgres_setup") -class TestPostgres: - def test_site_creation(self, compose: Compose): - compose.bench( - "new-site", - "test-pg-site.localhost", - "--db-type", - "postgres", - "--admin-password", - "admin", - ) +import os +from pathlib import Path +from typing import Any + +import pytest + +from tests.conftest import S3ServiceResult +from tests.utils import Compose, check_url_content + +BACKEND_SERVICES = ( + "backend", + "queue-short", + "queue-long", + "scheduler", +) + + +@pytest.mark.parametrize("service", BACKEND_SERVICES) +def test_links_in_backends(service: str, compose: Compose, python_path: str): + filename = "_check_connections.py" + compose("cp", f"tests/{filename}", f"{service}:/tmp/") + compose.exec(service, python_path, f"/tmp/{filename}") + + +def index_cb(text: str): + if "404 page not found" not in text: + return text[:200] + + +def api_cb(text: str): + if '"message"' in text: + return text + + +def assets_cb(text: str): + if text: + return text[:200] + + +@pytest.mark.parametrize( + ("url", "callback"), (("/", index_cb), ("/api/method/ping", api_cb)) +) +def test_endpoints(url: str, callback: Any, frappe_site: str): + check_url_content( + url=f"http://127.0.0.1{url}", callback=callback, site_name=frappe_site + ) + + +@pytest.mark.skipif( + os.environ["FRAPPE_VERSION"][0:3] == "v12", reason="v12 doesn't have the asset" +) +def test_assets_endpoint(frappe_site: str): + check_url_content( + url=f"http://127.0.0.1/assets/frappe/images/frappe-framework-logo.svg", + callback=assets_cb, + site_name=frappe_site, + ) + + +def test_files_reachable(frappe_site: str, tmp_path: Path, compose: Compose): + content = "lalala\n" + file_path = tmp_path / "testfile.txt" + + with file_path.open("w") as f: + f.write(content) + + compose( + "cp", + str(file_path), + f"backend:/home/frappe/frappe-bench/sites/{frappe_site}/public/files/", + ) + + def callback(text: str): + if text == content: + return text + + check_url_content( + url=f"http://127.0.0.1/files/{file_path.name}", + callback=callback, + site_name=frappe_site, + ) + + +@pytest.mark.parametrize("service", BACKEND_SERVICES) +@pytest.mark.usefixtures("frappe_site") +def test_frappe_connections_in_backends( + service: str, python_path: str, compose: Compose +): + filename = "_ping_frappe_connections.py" + compose("cp", f"tests/{filename}", f"{service}:/tmp/") + compose.exec( + "-w", + "/home/frappe/frappe-bench/sites", + service, + python_path, + f"/tmp/{filename}", + ) + + +def test_push_backup( + frappe_site: str, + s3_service: S3ServiceResult, + compose: Compose, +): + restic_password = "secret" + compose.bench("--site", frappe_site, "backup", "--with-files") + restic_args = [ + "--env=RESTIC_REPOSITORY=s3:http://minio:9000/frappe", + f"--env=AWS_ACCESS_KEY_ID={s3_service.access_key}", + f"--env=AWS_SECRET_ACCESS_KEY={s3_service.secret_key}", + f"--env=RESTIC_PASSWORD={restic_password}", + ] + compose.exec(*restic_args, "backend", "restic", "init") + compose.exec(*restic_args, "backend", "restic", "backup", "sites") + compose.exec(*restic_args, "backend", "restic", "snapshots") + + +def test_https(frappe_site: str, compose: Compose): + compose("-f", "overrides/compose.https.yaml", "up", "-d") + check_url_content(url="https://127.0.0.1", callback=index_cb, site_name=frappe_site) + + +@pytest.mark.usefixtures("erpnext_setup") +class TestErpnext: + @pytest.mark.parametrize( + ("url", "callback"), + ( + ( + "/api/method/erpnext.templates.pages.search_help.get_help_results_sections?text=help", + api_cb, + ), + ("/assets/erpnext/js/setup_wizard.js", assets_cb), + ), + ) + def test_endpoints(self, url: str, callback: Any, erpnext_site: str): + check_url_content( + url=f"http://127.0.0.1{url}", callback=callback, site_name=erpnext_site + ) + + +@pytest.mark.usefixtures("postgres_setup") +class TestPostgres: + def test_site_creation(self, compose: Compose): + compose.bench( + "new-site", + "test-pg-site.localhost", + "--db-type", + "postgres", + "--admin-password", + "admin", + ) diff --git a/tests/utils.py b/tests/utils.py index fc12198b..d6d35fb0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,89 +1,89 @@ -import os -import ssl -import subprocess -import sys -import time -from contextlib import suppress -from typing import Callable, Optional -from urllib.error import HTTPError, URLError -from urllib.request import Request, urlopen - -CI = os.getenv("CI") - - -class Compose: - def __init__(self, project_name: str, env_file: str): - self.project_name = project_name - self.base_cmd = ( - "docker", - "compose", - "-p", - project_name, - "--env-file", - env_file, - ) - - def __call__(self, *cmd: str) -> None: - file_args = [ - "-f", - "compose.yaml", - "-f", - "overrides/compose.proxy.yaml", - "-f", - "overrides/compose.mariadb.yaml", - "-f", - "overrides/compose.redis.yaml", - ] - if CI: - file_args += ("-f", "tests/compose.ci.yaml") - - args = self.base_cmd + tuple(file_args) + cmd - subprocess.check_call(args) - - def exec(self, *cmd: str) -> None: - if sys.stdout.isatty(): - self("exec", *cmd) - else: - self("exec", "-T", *cmd) - - def stop(self) -> None: - # Stop all containers in `test` project if they are running. - # We don't care if it fails. - with suppress(subprocess.CalledProcessError): - subprocess.check_call(self.base_cmd + ("down", "-v", "--remove-orphans")) - - def bench(self, *cmd: str) -> None: - self.exec("backend", "bench", *cmd) - - -def check_url_content( - url: str, callback: Callable[[str], Optional[str]], site_name: str -): - request = Request(url, headers={"Host": site_name}) - - # This is needed to check https override - ctx = ssl.create_default_context() - ctx.check_hostname = False - ctx.verify_mode = ssl.CERT_NONE - - for _ in range(100): - try: - response = urlopen(request, context=ctx) - - except HTTPError as exc: - if exc.code not in (404, 502): - raise - - except URLError: - pass - - else: - text: str = response.read().decode() - ret = callback(text) - if ret: - print(ret) - return - - time.sleep(0.1) - - raise RuntimeError(f"Couldn't ping {url}") +import os +import ssl +import subprocess +import sys +import time +from contextlib import suppress +from typing import Callable, Optional +from urllib.error import HTTPError, URLError +from urllib.request import Request, urlopen + +CI = os.getenv("CI") + + +class Compose: + def __init__(self, project_name: str, env_file: str): + self.project_name = project_name + self.base_cmd = ( + "docker", + "compose", + "-p", + project_name, + "--env-file", + env_file, + ) + + def __call__(self, *cmd: str) -> None: + file_args = [ + "-f", + "compose.yaml", + "-f", + "overrides/compose.proxy.yaml", + "-f", + "overrides/compose.mariadb.yaml", + "-f", + "overrides/compose.redis.yaml", + ] + if CI: + file_args += ("-f", "tests/compose.ci.yaml") + + args = self.base_cmd + tuple(file_args) + cmd + subprocess.check_call(args) + + def exec(self, *cmd: str) -> None: + if sys.stdout.isatty(): + self("exec", *cmd) + else: + self("exec", "-T", *cmd) + + def stop(self) -> None: + # Stop all containers in `test` project if they are running. + # We don't care if it fails. + with suppress(subprocess.CalledProcessError): + subprocess.check_call(self.base_cmd + ("down", "-v", "--remove-orphans")) + + def bench(self, *cmd: str) -> None: + self.exec("backend", "bench", *cmd) + + +def check_url_content( + url: str, callback: Callable[[str], Optional[str]], site_name: str +): + request = Request(url, headers={"Host": site_name}) + + # This is needed to check https override + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + + for _ in range(100): + try: + response = urlopen(request, context=ctx) + + except HTTPError as exc: + if exc.code not in (404, 502): + raise + + except URLError: + pass + + else: + text: str = response.read().decode() + ret = callback(text) + if ret: + print(ret) + return + + time.sleep(0.1) + + raise RuntimeError(f"Couldn't ping {url}")