Skip to content

Erpnext

erpnext Picture from erpnext.com

To keep in line with the instructions in the erpnext helm repo, we will be using helm and namespaces.

Note, make sure longhorn is installed. It is possible to avvoid it by pre-creating a local-storage pvc and referencing it in erpnext_values, but for testing I found it easier to install longhorn and reference it as storageClass instead. This way you will have RWX (the volume cna be mounted as read-write by many nodes) compatible volumes ready for scaling as well.

Add repo

helm repo add frappe https://helm.erpnext.com && \
helm repo update

Create namespace

kubectl create namespace erpnext

Install with helm

helm install frappe-bench -n erpnext -f erpnext_values.yaml frappe/erpnext
erpnext_values.yaml
persistence:
  worker:
    storageClass: "longhorn"

mariadb:
  enabled: true

global:
  storageClass: "longhorn"

Watch creation

Wait for the containers to be created and settle down

kubectl get pods -n erpnext --watch

You should eventually see something like this

NAME                                                   READY   STATUS      RESTARTS      AGE
frappe-bench-erpnext-conf-bench-20230204220652-rjtxx   0/1     Completed   0             100s
frappe-bench-erpnext-gunicorn-7989dfb6c8-p7njf         1/1     Running     0             100s
frappe-bench-erpnext-nginx-684d567845-5f845            1/1     Running     0             100s
frappe-bench-erpnext-scheduler-86648bd8d7-h6vrf        1/1     Running     3 (62s ago)   100s
frappe-bench-erpnext-socketio-75bd4648b6-gsbqx         1/1     Running     3 (49s ago)   100s
frappe-bench-erpnext-worker-d-74f5b8fdd4-xg6rp         1/1     Running     3 (55s ago)   100s
frappe-bench-erpnext-worker-l-5dcbbc79d8-8bd8g         1/1     Running     3 (51s ago)   100s
frappe-bench-erpnext-worker-s-54968545c4-2mtkv         1/1     Running     3 (59s ago)   100s
frappe-bench-mariadb-0                                 1/1     Running     0             100s
frappe-bench-redis-cache-master-0                      1/1     Running     0             100s
frappe-bench-redis-queue-master-0                      1/1     Running     0             100s
frappe-bench-redis-socketio-master-0                   1/1     Running     0             100s

Create a new site template

helm template frappe-bench -n erpnext frappe/erpnext -f erpnext_newsite_values.yaml -s templates/job-create-site.yaml > erpnext_newsite_manifest.yaml

The values:

erpnext_newsite_values.yaml
persistence:
  worker:
    storageClass: "longhorn"

global:
  storageClass: "longhorn"

jobs:
  createSite:
    enabled: true
    siteName: "erpnext.k3s"
    adminPassword: "secret"

Templated manifest:

erpnext_newsite_manifest.yaml
---
# Source: erpnext/templates/job-create-site.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: frappe-bench-erpnext-new-site-20230204221209
  labels:
    helm.sh/chart: erpnext-6.0.5
    app.kubernetes.io/name: erpnext
    app.kubernetes.io/instance: frappe-bench
    app.kubernetes.io/version: "v14.15.1"
    app.kubernetes.io/managed-by: Helm
spec:
  backoffLimit: 0
  template:
    spec:
      serviceAccountName: frappe-bench-erpnext
      securityContext:
        supplementalGroups:
          - 1000
      initContainers:
        - name: validate-config
          image: "frappe/erpnext:v14.15.1"
          imagePullPolicy: IfNotPresent
          command: ["bash", "-c"]
          args:
            - >
              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 > 600 )); then
                  echo "could not find sites/common_site_config.json with required keys";
                  exit 1
                fi
              done;
              echo "sites/common_site_config.json found";
          resources: {}
          securityContext:
            capabilities:
              add:
                - CAP_CHOWN
          volumeMounts:
            - name: sites-dir
              mountPath: /home/frappe/frappe-bench/sites
      containers:
        - name: create-site
          image: "frappe/erpnext:v14.15.1"
          imagePullPolicy: IfNotPresent
          command: ["bash", "-c"]
          args:
            - >
              bench new-site $(SITE_NAME)
              --no-mariadb-socket
              --db-type=$(DB_TYPE)
              --db-host=$(DB_HOST)
              --db-port=$(DB_PORT)
              --admin-password=$(ADMIN_PASSWORD)
              --mariadb-root-username=$(DB_ROOT_USER)
              --mariadb-root-password=$(DB_ROOT_PASSWORD)
              --install-app=erpnext
              ;rm -f currentsite.txt
          env:
            - name: "SITE_NAME"
              value: "erpnext.k3s"
            - name: "DB_TYPE"
              value: mariadb
            - name: "DB_HOST"
              value: frappe-bench-mariadb
            - name: "DB_PORT"
              value: "3306"
            - name: "DB_ROOT_USER"
              value: "root"
            - name: "DB_ROOT_PASSWORD"
              valueFrom:
                secretKeyRef:
                  key: mariadb-root-password
                  name: frappe-bench-mariadb
            - name: "ADMIN_PASSWORD"
              value: "secret"
          resources: {}
          securityContext:
            capabilities:
              add:
                - CAP_CHOWN
          volumeMounts:
            - name: sites-dir
              mountPath: /home/frappe/frappe-bench/sites
            - name: logs
              mountPath: /home/frappe/frappe-bench/logs
      restartPolicy: Never
      volumes:
        - name: sites-dir
          persistentVolumeClaim:
            claimName: frappe-bench-erpnext
            readOnly: false
        - name: logs
          emptyDir: {}

Apply the templated manifest

kubectl apply -n erpnext -f erpnext_newsite_manifest.yaml

View site creation

Get jobs

kubectl get jobs -n erpnext

Result

NAME                                             COMPLETIONS   DURATION   AGE
frappe-bench-erpnext-conf-bench-20230204220652   1/1           70s        9m25s
frappe-bench-erpnext-new-site-20230204221209     0/1           8s         8s

Get logs for the job

kubectl logs job/frappe-bench-erpnext-new-site-20230204221209 -f -n erpnext

Result

Defaulted container "create-site" out of: create-site, validate-config (init)

Installing frappe...
Updating DocTypes for frappe        : [========================================] 100%
Updating country info               : [========================================] 100%
Updating Dashboard for frappe

Installing erpnext...
Updating DocTypes for erpnext       : [========================================] 100%
Updating customizations for Address
Updating customizations for Contact
Updating Dashboard for erpnext
*** Scheduler is disabled ***

Add ingress

Without tls

Apply ingress

kubectl apply -f erpnext_notls_ingress.yaml -n erpnext

Add fake domain to /etc/hosts

Add something like this to the last line (use your server ip and desired fake hostname):

12.345.67.89 erpnext.k3s

or with a commmand:

sudo -- sh -c "echo '<server ip> erpnext.k3s' >> /etc/hosts"

Go to site

http://erpnext.k3s

With tls

Note

If you first followed the steps above with a non tls version, you will have a site called erpnext.k3s. Erpnext expects a host header with this domain in it, and this is why we set it in /etc/hosts in the non-tls version.

So when you want a tls version, you have two choices.

Reinstall
Edit the values in the manifests used above to something that is correct, like erpnext.your.domain.com when installing / reinstalling.

OR

Add Middleware
Add a middleware that replaces erpnext.your.domain.com with erpnext.k3s :

kubectl apply -f erpnext_replace_host_header_middleware.yaml
erpnext_replace_host_header_middleware.yaml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: replace-host
spec:
  headers:
    customRequestHeaders:
      host: "erpnext.k3s"

Set environment variables

export DOMAIN=dog.example.com
export EMAIL=name@example.com

Apply manifest

cat ./manifests/erpnext_tls_ingress.yaml | envsubst | kubectl apply -f -
erpnext_tls_ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: erpnext-tls-ingress
  annotations:
    spec.ingressClassName: traefik
    cert-manager.io/cluster-issuer: letsencrypt-prod
    traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd, default-replace-host@kubernetescrd
spec:
  rules:
    - host: erpnext.${DOMAIN}
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frappe-bench-erpnext
                port:
                  number: 8080
  tls:
    - secretName: erpnext-tls
      hosts:
        - erpnext.${DOMAIN}

Go to site

https://erpnext.yourdomain.com

Credentials

username: Administrator
password: secret

Enjoy

erpnext