Frappe ERPNext: ERP maybe?

Published: Jun 15, 2023 by Isaac Johnson

I had a note to checkout Frappe cloud and ERPNext. The idea is it is an open-source Enterprise Resource Planning suite that is easy to install and host. We will try and set it up and explore the cloud offering as well.

We will try and set up the Helm chart in Kubernetes, the Docker install with VS Code and lastly the Cloud Offering.

Helm install

Add the Helm repo

$ helm repo add frappe https://helm.erpnext.com
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/builder/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/builder/.kube/config
"frappe" has been added to your repositories

Helm Repo update

$ helm repo update
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/builder/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/builder/.kube/config
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "backstage" chart repository
...Successfully got an update from the "frappe" chart repository
...Successfully got an update from the "bitnami" chart repository
...Successfully got an update from the "deliveryhero" chart repository
Update Complete. ⎈Happy Helming!⎈

The chart will want our Storage Class name, so let’s fetch that

$ kubectl get sc
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  10d

We can now install using helm

$ helm upgrade --install frappe-bench --namespace erpnext --create-namespace fra
ppe/erpnext --set persistence.worker.storageClass=local-path
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/builder/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/builder/.kube/config
Release "frappe-bench" does not exist. Installing it now.
NAME: frappe-bench
LAST DEPLOYED: Sat Jun 10 21:11:02 2023
NAMESPACE: erpnext
STATUS: deployed
REVISION: 1
NOTES:
Frappe/ERPNext Release deployed

Release Name: frappe-bench-erpnext

Wait for the pods to start.
To create sites and other resources, refer:
https://github.com/frappe/helm/blob/main/erpnext/README.md

Frequently Asked Questions:
https://helm.erpnext.com/faq

We can now see services in the namespace

$ kubectl get svc -n erpnext
NAME                                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
frappe-bench-redis-queue-headless      ClusterIP   None            <none>        6379/TCP   17m
frappe-bench-redis-cache-headless      ClusterIP   None            <none>        6379/TCP   17m
frappe-bench-redis-socketio-headless   ClusterIP   None            <none>        6379/TCP   17m
frappe-bench-erpnext-socketio          ClusterIP   10.43.253.10    <none>        9000/TCP   17m
frappe-bench-erpnext-gunicorn          ClusterIP   10.43.166.50    <none>        8000/TCP   17m
frappe-bench-mariadb                   ClusterIP   10.43.81.56     <none>        3306/TCP   17m
frappe-bench-redis-cache-master        ClusterIP   10.43.220.193   <none>        6379/TCP   17m
frappe-bench-erpnext                   ClusterIP   10.43.243.212   <none>        8080/TCP   17m
frappe-bench-redis-queue-master        ClusterIP   10.43.95.27     <none>        6379/TCP   17m
frappe-bench-redis-socketio-master     ClusterIP   10.43.188.248   <none>        6379/TCP   17m

We can see the pods created

$ kubectl get pod -n erpnext
NAME                                                   READY   STATUS    RESTARTS   AGE
frappe-bench-redis-cache-master-0                      1/1     Running   0          17m
frappe-bench-redis-socketio-master-0                   1/1     Running   0          17m
frappe-bench-redis-queue-master-0                      1/1     Running   0          17m
frappe-bench-mariadb-0                                 1/1     Running   0          17m
frappe-bench-erpnext-worker-s-5598db657c-xw5x5         0/1     Pending   0          17m
frappe-bench-erpnext-nginx-79d8b9db9d-vhlr2            0/1     Pending   0          17m
frappe-bench-erpnext-worker-l-9457499bf-xtj86          0/1     Pending   0          17m
frappe-bench-erpnext-worker-d-c7f58cbb7-vt76x          0/1     Pending   0          17m
frappe-bench-erpnext-socketio-84c7d76fb9-7pdtx         0/1     Pending   0          17m
frappe-bench-erpnext-conf-bench-20230610211102-c686x   0/1     Pending   0          17m
frappe-bench-erpnext-gunicorn-65f5575bc5-ktkns         0/1     Pending   0          17m
frappe-bench-erpnext-scheduler-775ddbc67d-7trm2        0/1     Pending   0          17m

Let’s see what PVCs were requested

$ kubectl get pvc -n erpnext
NAME                          STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
frappe-bench-erpnext          Pending                                                                        local-path     20m
data-frappe-bench-mariadb-0   Bound     pvc-0eb3baf3-d49f-4008-b0e4-8af609c4a3b2   8Gi        RWO            local-path     20m

Looking at the PVC details we can see it’s do to RWO instead of RWM

Events:
  Type     Reason                Age                         From
                                        Message
  ----     ------                ----                        ----
                                        -------
  Normal   WaitForFirstConsumer  9m23s                       persistentvolume-controller
                                        waiting for first consumer to be created before binding
  Normal   Provisioning          <invalid> (x8 over 9m22s)   rancher.io/local-path_local-path-provisioner-957fdf8bc-lldmr_57a194f4-4fd7-4223-9093-dcb9fc320a9c  External provisioner is provisioning volume for claim "erpnext/frappe-bench-erpnext"
  Warning  ProvisioningFailed    <invalid> (x8 over 9m22s)   rancher.io/local-path_local-path-provisioner-957fdf8bc-lldmr_57a194f4-4fd7-4223-9093-dcb9fc320a9c  failed to provision volume with StorageClass "local-path": Only support ReadWriteOnce access mode
  Normal   ExternalProvisioning  <invalid> (x83 over 9m22s)  persistentvolume-controller
                                        waiting for a volume to be created, either by external provisioner "rancher.io/local-path" or manually created by system administrator

with the PVC spec

$ kubectl get pvc frappe-bench-erpnext -n erpnext -o yaml | tail -n 10
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 8Gi
  storageClassName: local-path
  volumeMode: Filesystem
status:
  phase: Pending

I see plenty of volumes

$ kubectl get pv -n erpnext
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM
             STORAGECLASS   REASON   AGE
pvc-fe8b7fa3-a3a5-4b56-bde0-a2396686c69f   8Gi        RWO            Delete           Bound    default/data-backstage-postgresql-0   local-path              7d10h
pvc-d83ee42f-648e-4319-bb6b-dd937c3b6ccf   10Gi       RWO            Delete           Bound    kube-logging/data-es-cluster-0        local-path              7d5h
pvc-2caba020-aeb4-4138-9e5f-1a0f9d2ce3a7   10Gi       RWO            Delete           Bound    kube-logging/data-es-cluster-1        local-path              7d5h
pvc-0d816603-4186-4b04-a5b3-2077379b1a67   10Gi       RWO            Delete           Bound    kube-logging/data-es-cluster-2        local-path              7d5h
pvc-cb82315d-d3fd-4d2d-978b-b3b89d9ec6d8   10Gi       RWO            Delete           Bound    default/data-loki-write-0             local-path              7d5h
pvc-d5e51f3e-9282-48a8-9cc3-9e2ec3662bb2   10Gi       RWO            Delete           Bound    default/data-loki-write-2             local-path              7d5h
pvc-daf8405a-3303-4390-9b2a-753816e0ca2a   10Gi       RWO            Delete           Bound    default/data-loki-write-1             local-path              7d5h
pvc-aad6dc45-2446-468f-9a67-27bbcec439b6   10Gi       RWO            Delete           Bound    default/data-loki-backend-1           local-path              7d5h
pvc-becf1805-9674-43a7-80f2-56a45da6b769   10Gi       RWO            Delete           Bound    default/data-loki-backend-2           local-path              7d5h
pvc-cc3aba07-ece7-4123-85c4-3a22e5ca6f50   10Gi       RWO            Delete           Bound    default/data-loki-backend-0           local-path              7d5h

I changed clusters to one with a different storage class

$ helm upgrade --install frappe-bench --namespace erpnext --create-namespace frappe/erpnext --set persistence.worker.storageClass=managed-nfs-storage
WARNING: Kubernetes configuration file is group-readable. This is insecure. Location: /home/builder/.kube/config
WARNING: Kubernetes configuration file is world-readable. This is insecure. Location: /home/builder/.kube/config
Release "frappe-bench" does not exist. Installing it now.
NAME: frappe-bench
LAST DEPLOYED: Sat Jun 10 21:32:41 2023
NAMESPACE: erpnext
STATUS: deployed
REVISION: 1
NOTES:
Frappe/ERPNext Release deployed

Release Name: frappe-bench-erpnext

Wait for the pods to start.
To create sites and other resources, refer:
https://github.com/frappe/helm/blob/main/erpnext/README.md

Frequently Asked Questions:
https://helm.erpnext.com/faq

This time it started to launch pods

$ kubectl get pods -n erpnext
NAME                                                   READY   STATUS              RESTARTS      AGE
frappe-bench-erpnext-gunicorn-7755c76b48-m5h7t         0/1     ContainerCreating   0             105s
frappe-bench-erpnext-scheduler-798bf5dc55-b8tq8        0/1     ContainerCreating   0             105s
frappe-bench-erpnext-nginx-7bd486cb49-l2vb8            0/1     ContainerCreating   0             105s
frappe-bench-erpnext-worker-l-c5dbcf5d-vgnm4           0/1     ContainerCreating   0             105s
frappe-bench-erpnext-worker-s-f6d596f7d-fxlxt          0/1     ContainerCreating   0             105s
frappe-bench-erpnext-conf-bench-20230610213241-vk98s   0/1     Init:0/1            0             105s
frappe-bench-redis-queue-master-0                      1/1     Running             0             105s
frappe-bench-redis-cache-master-0                      1/1     Running             0             105s
frappe-bench-redis-socketio-master-0                   1/1     Running             0             105s
frappe-bench-erpnext-socketio-7d6978bb8-fcq4l          0/1     CrashLoopBackOff    3 (19s ago)   104s
frappe-bench-erpnext-worker-d-5bf6fc687b-zb2zr         0/1     CrashLoopBackOff    3 (7s ago)    104s

After a while

$ kubectl get pods -n erpnext
NAME                                                   READY   STATUS             RESTARTS        AGE
frappe-bench-redis-queue-master-0                      1/1     Running            0               8m40s
frappe-bench-redis-cache-master-0                      1/1     Running            0               8m40s
frappe-bench-redis-socketio-master-0                   1/1     Running            0               8m40s
frappe-bench-erpnext-nginx-7bd486cb49-l2vb8            1/1     Running            0               8m40s
frappe-bench-erpnext-gunicorn-7755c76b48-m5h7t         1/1     Running            0               8m40s
frappe-bench-erpnext-scheduler-798bf5dc55-b8tq8        1/1     Running            0               8m40s
frappe-bench-erpnext-conf-bench-20230610213241-vk98s   0/1     Completed          0               8m40s
frappe-bench-erpnext-worker-s-f6d596f7d-fxlxt          1/1     Running            2 (2m21s ago)   8m40s
frappe-bench-erpnext-worker-l-c5dbcf5d-vgnm4           1/1     Running            2 (2m23s ago)   8m40s
frappe-bench-erpnext-worker-d-5bf6fc687b-zb2zr         1/1     Running            6 (4m44s ago)   8m39s
frappe-bench-erpnext-socketio-7d6978bb8-fcq4l          0/1     CrashLoopBackOff   6 (67s ago)     8m39s

I restarted the pod and it came up

$ kubectl get pods -l app.kubernetes.io/instance=frappe-bench-socketio -n erpnext
NAME                                            READY   STATUS    RESTARTS   AGE
frappe-bench-erpnext-socketio-7d6978bb8-mtv9n   1/1     Running   0          17s

I can now port-forward

$ kubectport-forward svc/frappe-bench-erpnext -n erpnext 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080

/content/images/2023/06/frappe-01.png

Setting up a website

We’ll create a password for the user

$ kubectl create secret -n erpnext generic mariadb-root-password --from-literal=password=freshbrewedpassword
secret/mariadb-root-password created

We just need a batch job

$ cat createSite.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: create-erp-example-com
spec:
  backoffLimit: 0
  template:
    spec:
      securityContext:
        supplementalGroups: [1000]
      containers:
      - name: create-site
        image: frappe/erpnext:v14.27.1
        args: ["new"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
          - name: sites-dir
            mountPath: /home/frappe/frappe-bench/sites
        env:
          - name: "SITE_NAME"
            value: erpnext-example.svc.cluster.local
          - name: "DB_ROOT_USER"
            value: root
          - name: "MYSQL_ROOT_PASSWORD"
            valueFrom:
              secretKeyRef:
                name: mariadb-root-password
                key: password
          - name: "ADMIN_PASSWORD"
            value: super_secret_password
          - name: "INSTALL_APPS"
            value: "erpnext"
      restartPolicy: Never
      volumes:
        - name: sites-dir
          persistentVolumeClaim:
            claimName: frappe-bench-erpnext
            readOnly: false
$ kubectl apply -f createSite.yaml -n erpnext
job.batch/create-erp-example-com created

I actually worked the job for a while.

It seems “new” hasn’t been part of the bench command, as far as I can see, for a while.

What was closer was

apiVersion: batch/v1
kind: Job
metadata:
  name: create-erp-example-com
spec:
  backoffLimit: 0
  template:
    spec:
      securityContext:
        supplementalGroups: [1000]
      containers:
      - name: create-site
        image: frappe/erpnext-worker:v13
        args: ["bench","new-site","--db-root-username $DB_ROOT_USER","--db-root-password $MYSQL_ROOT_PASSWORD","--db-type mariadb","--admin-password $ADMIN_PASSWORD","--install-app $INSTALL_APPS","$SITE_NAME"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
          - name: sites-dir
            mountPath: /home/frappe/frappe-bench/sites
        env:
          - name: "SITE_NAME"
            value: erpnext-example.svc.cluster.local
          - name: "DB_ROOT_USER"
            value: root
          - name: "MYSQL_ROOT_PASSWORD"
            valueFrom:
              secretKeyRef:
                name: mariadb-root-password
                key: password
          - name: "ADMIN_PASSWORD"
            value: super_secret_password
          - name: "INSTALL_APPS"
            value: "erpnext"
      restartPolicy: Never
      volumes:
        - name: sites-dir
          persistentVolumeClaim:
            claimName: frappe-bench-erpnext
            readOnly: false

The errors to the DB had me trying commands on a worker directly:

frappe@frappe-bench-erpnext-worker-s-f6d596f7d-fxlxt:~/frappe-bench$ mysql --host=10.43.121.34 --port=3306 --user=root  --password=freshbrewedpassword
ERROR 2002 (HY000): Can't connect to MySQL server on '10.43.121.34' (115)

I realized that frappe installed a MariaDB service:

$ kubectl get svc frappe-bench-mariadb -o yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    meta.helm.sh/release-name: frappe-bench
    meta.helm.sh/release-namespace: erpnext
  creationTimestamp: "2023-06-11T02:45:04Z"
  labels:
    app.kubernetes.io/component: primary
    app.kubernetes.io/instance: frappe-bench
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/name: mariadb
    helm.sh/chart: mariadb-11.4.2
  name: frappe-bench-mariadb
  namespace: erpnext
  resourceVersion: "156653268"
  uid: 665d20c7-014c-4ac3-8c77-1240af1d22a9
spec:
  clusterIP: 10.43.121.34
  clusterIPs:
  - 10.43.121.34
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - name: mysql
    port: 3306
    protocol: TCP
    targetPort: mysql
  selector:
    app.kubernetes.io/component: primary
    app.kubernetes.io/instance: frappe-bench
    app.kubernetes.io/name: mariadb
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}

But no actual mariadb instance

$ kubectl get pods -l app.kubernetes.io/name=mariadb
No resources found in erpnext namespace.

Let’s create a MariaDB to solve this

$ cat frappeMariaDb.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mariadb-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: primary
      app.kubernetes.io/instance: frappe-bench
      app.kubernetes.io/name: mariadb
  template:
    metadata:
      labels:
        app.kubernetes.io/component: primary
        app.kubernetes.io/instance: frappe-bench
        app.kubernetes.io/name: mariadb
    spec:
      containers:
      - name: mariadb
        image: mariadb
        ports:
        - containerPort: 3306
        env:
        #- name: MARIADB_RANDOM_ROOT_PASSWORD
        - name: MARIADB_ALLOW_EMPTY_ROOT_PASSWORD
          value: "0" # if it is 1 and root_password is set, root_password takes precedance
        - name: MARIADB_ROOT_PASSWORD
          value: freshbrewedpassword

$ kubectl apply -f frappeMariaDb.yaml
deployment.apps/mariadb-deployment created

$ kubectl get pods -l app.kubernetes.io/name=mariadb
NAME                                  READY   STATUS    RESTARTS   AGE
mariadb-deployment-7f64864f49-n6bvd   1/1     Running   0          61s

Interestingly, I can reach the pod directly

frappe@frappe-bench-erpnext-worker-s-f6d596f7d-fxlxt:~/frappe-bench$ mysql --host=10.42.1.221 --port=3306 --user=root  -
-password=freshbrewedpassword
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 11.0.2-MariaDB-1:11.0.2+maria~ubu2204 mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> \q

but not via the service

frappe@frappe-bench-erpnext-worker-s-f6d596f7d-fxlxt:~/frappe-bench$ mysql --host=10.43.121.34 --port=3306 --user=root  --password=freshbrewedpassword
^C
frappe@frappe-bench-erpnext-worker-s-f6d596f7d-fxlxt:~/frappe-bench$ mysql --host=frappe-bench-mariadb --port=3306 --use
r=root  --password=freshbrewedpassword
ERROR 2002 (HY000): Can't connect to MySQL server on 'frappe-bench-mariadb' (115)
frappe@frappe-bench-erpnext-worker-s-f6d596f7d-fxlxt:~/frappe-bench$ mysql --host=frappe-bench-mariadb.erpnext.svc.cluster.local --port=3306 --user=root  --password=freshbrewedpassword
ERROR 2002 (HY000): Can't connect to MySQL server on 'frappe-bench-mariadb.erpnext.svc.cluster.local' (115)

Even tho that should work

$ kubectl get pods -l app.kubernetes.io/name=mariadb,app.kubernetes.io/instance=frappe-bench,app.kubernetes.io/name=mariadb
NAME                                  READY   STATUS    RESTARTS   AGE
mariadb-deployment-7f64864f49-n6bvd   1/1     Running   0          4m23s

My next issue was it didn’t like the OOTB MariaDB setup

$ kubectl logs create-erp-example-com-btmzs
For key collation_server. Expected value utf8mb4_unicode_ci, found value utf8mb4_general_ci
================================================================================
Creation of your site - erpnext-example.svc.cluster.local failed because MariaDB is not properly
configured.  If using version 10.2.x or earlier, make sure you use the
the Barracuda storage engine.

Please verify the settings above in MariaDB's my.cnf.  Restart MariaDB.  And
then run `bench new-site erpnext-example.svc.cluster.local` again.


================================================================================

It took a few tries, but I managed to work out the following YAML for a MariaDB configured the way Frappe ERPNext would like it

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mariadb-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/component: primary
      app.kubernetes.io/instance: frappe-bench
      app.kubernetes.io/name: mariadb
  template:
    metadata:
      labels:
        app.kubernetes.io/component: primary
        app.kubernetes.io/instance: frappe-bench
        app.kubernetes.io/name: mariadb
    spec:
      containers:
      - name: mariadb
        image: mariadb
        ports:
        - containerPort: 3306 
        env:
        #- name: MARIADB_RANDOM_ROOT_PASSWORD
        - name: MARIADB_ALLOW_EMPTY_ROOT_PASSWORD
          value: "0" # if it is 1 and root_password is set, root_password takes precedance
        - name: MARIADB_ROOT_PASSWORD
          value: freshbrewedpassword
        volumeMounts:
        - name: configmap
          mountPath: /etc/mysql/mariadb.conf.d
        - name: data
          mountPath: /var/lib/mariadb
      volumes:
        - name: configmap
          configMap:
            name: frappe-mariadb-cnf
        - name: data
          persistentVolumeClaim:
            claimName: frappe-mariadb-data-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: frappe-mariadb-data-pvc
spec:
  storageClassName: local-path
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 8Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: frappe-mariadb-cnf
  labels: 
    app.kubernetes.io/component: primary
    app.kubernetes.io/instance: frappe-bench
    app.kubernetes.io/name: mariadb
data:
  my.cnf: |-
    [mysqld]
    character-set-client-handshake = FALSE
    character-set-server = utf8mb4
    collation-server = utf8mb4_unicode_ci

    [mysql]
    default-character-set = utf8mb4

The create job had also been updated to pass in the parameters the way the newer bench worker would like

$ cat createSite.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: create-erp-example2-com
spec:
  backoffLimit: 0
  template:
    spec:
      securityContext:
        supplementalGroups: [1000]
      containers:
      - name: create-site
        image: frappe/erpnext-worker:v13
        args: ["bench","new-site","--db-host $(DB_HOST)","--db-root-username $(DB_ROOT_USER)","--db-root-password $(MYSQL_ROOT_PASSWORD)","--db-type mariadb","--admin-password $(ADMIN_PASSWORD)","--install-app $(INSTALL_APPS)","$(SITE_NAME)"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
          - name: sites-dir
            mountPath: /home/frappe/frappe-bench/sites
        env:
          - name: "SITE_NAME"
            value: erpnext-example2.svc.cluster.local
          - name: "DB_HOST"
            value: "10.42.1.225"
          - name: "DB_ROOT_USER"
            value: root
          - name: "MYSQL_ROOT_PASSWORD"
            valueFrom:
              secretKeyRef:
                name: mariadb-root-password
                key: password
          - name: "ADMIN_PASSWORD"
            value: super_secret_password
          - name: "INSTALL_APPS"
            value: "erpnext"
      restartPolicy: Never
      volumes:
        - name: sites-dir
          persistentVolumeClaim:
            claimName: frappe-bench-erpnext
            readOnly: false

This time, after a sufficent time period passed, it completed

$ kubectl get jobs
NAME                                             COMPLETIONS   DURATION   AGE
frappe-bench-erpnext-conf-bench-20230610213241   1/1           6m43s      3d17h
create-erp-example2-com                          1/1           4m55s      6m14s

$ kubectl logs create-erp-example2-com-5bf9w

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

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

I can see the site was created, but am at a loss on how best to serve via port-forward

builder@LuiGi17:~/Workspaces/jekyll-blog$ kubectl exec -it frappe-bench-erpnext-nginx-7bd486cb49-l2vb8 -- /bin/bash
frappe@frappe-bench-erpnext-nginx-7bd486cb49-l2vb8:~/frappe-bench$ ls -ltra
total 32
drwxr-xr-x 3 frappe frappe 4096 Jun  9 16:57 config
-rw-r--r-- 1 frappe frappe  346 Jun  9 16:57 patches.txt
drwxr-xr-x 7 frappe frappe 4096 Jun  9 16:57 .
drwxr-xr-x 6 frappe frappe 4096 Jun  9 16:58 env
drwxr-xr-x 4 frappe frappe 4096 Jun  9 16:59 apps
drwxr-xr-x 1 frappe frappe 4096 Jun  9 17:00 ..
drwxr-xr-x 2 frappe frappe 4096 Jun 11 02:51 logs
drwxrwxrwx 6 frappe frappe 4096 Jun 14 20:15 sites
frappe@frappe-bench-erpnext-nginx-7bd486cb49-l2vb8:~/frappe-bench$ cd sites/
frappe@frappe-bench-erpnext-nginx-7bd486cb49-l2vb8:~/frappe-bench/sites$ ls
'$SITE_NAME'   assets                    erpnext-example.svc.cluster.local
 apps.txt      common_site_config.json   erpnext-example2.svc.cluster.local
frappe@frappe-bench-erpnext-nginx-7bd486cb49-l2vb8:~/frappe-bench/sites$ cat erpnext-example2.svc.cluster.local/
.test_log         locks/            private/          site_config.json
error-snapshots/  logs/             public/
frappe@frappe-bench-erpnext-nginx-7bd486cb49-l2vb8:~/frappe-bench/sites$ cat erpnext-example2.svc.cluster.local/site_config.json
{
 "db_host": "10.42.1.225",
 "db_name": "_986265b1f1ec718c",
 "db_password": "XxZgQ1Mieycd3szX",
 "db_type": "mariadb",
 "user_type_doctype_limit": {
  "employee_self_service": 10
 }

Because when doing port-forward

$ kubectl port-forward svc/frappe-bench-erpnext 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

Ningx seems stuck on looking for localhost or 127.0.0.1… not serving just any traffic.

/content/images/2023/06/frappe-02.png

and here

/content/images/2023/06/frappe-03.png

$ cat createLocalhostSite.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: create-erp-localhost
spec:
  backoffLimit: 0
  template:
    spec:
      securityContext:
        supplementalGroups: [1000]
      containers:
      - name: create-site
        image: frappe/erpnext-worker:v13
        args: ["bench","new-site","--db-host $(DB_HOST)","--db-root-username $(DB_ROOT_USER)","--db-root-password $(MYSQL_ROOT_PASSWORD)","--db-type mariadb","--admin-password $(ADMIN_PASSWORD)","--install-app $(INSTALL_APPS)","$(SITE_NAME)"]
        imagePullPolicy: IfNotPresent
        volumeMounts:
          - name: sites-dir
            mountPath: /home/frappe/frappe-bench/sites
        env:
          - name: "SITE_NAME"
            value: localhost
          - name: "DB_HOST"
            value: "10.42.1.225"
          - name: "DB_ROOT_USER"
            value: root
          - name: "MYSQL_ROOT_PASSWORD"
            valueFrom:
              secretKeyRef:
                name: mariadb-root-password
                key: password
          - name: "ADMIN_PASSWORD"
            value: super_secret_password
          - name: "INSTALL_APPS"
            value: "erpnext"
      restartPolicy: Never
      volumes:
        - name: sites-dir
          persistentVolumeClaim:
            claimName: frappe-bench-erpnext
            readOnly: false

$ kubectl apply -f createLocalhostSite.yaml
job.batch/create-erp-localhost created

When that finished

$ kubectl logs create-erp-localhost-vr4zd

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

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

Now when I port forward I get an error

$ kubectl port-forward svc/frappe-bench-erpnext 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080

/content/images/2023/06/frappe-04.png

NGinx didn’t give me any clues

$ kubectl logs frappe-bench-erpnext-nginx-7bd486cb49-l2vb8
PROXY_READ_TIMEOUT defaulting to 120
CLIENT_MAX_BODY_SIZE defaulting to 50m
127.0.0.1 - - [11/Jun/2023:02:58:39 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [11/Jun/2023:02:58:46 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [11/Jun/2023:02:58:46 +0000] "GET /favicon.ico HTTP/1.1" 404 111 "http://localhost:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [11/Jun/2023:02:59:10 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [11/Jun/2023:02:59:16 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [11/Jun/2023:02:59:16 +0000] "GET /favicon.ico HTTP/1.1" 404 111 "http://127.0.0.1:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [11/Jun/2023:03:04:37 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:24:43 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:24:44 +0000] "GET /favicon.ico HTTP/1.1" 404 111 "http://localhost:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:24:54 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:24:54 +0000] "GET /favicon.ico HTTP/1.1" 404 111 "http://127.0.0.1:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:30:27 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:31:02 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:31:07 +0000] "GET / HTTP/1.1" 404 111 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:43:26 +0000] "GET / HTTP/1.1" 500 141 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:43:59 +0000] "GET / HTTP/1.1" 500 141 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:45:25 +0000] "GET / HTTP/1.1" 500 141 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [14/Jun/2023:20:45:29 +0000] "GET / HTTP/1.1" 500 141 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"

However, no matter what i did, i would get 500s in the nginx logs

127.0.0.1 - - [15/Jun/2023:22:20:35 +0000] "GET /apps.txt HTTP/1.1" 500 141 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [15/Jun/2023:22:27:29 +0000] "GET /apps.txt HTTP/1.1" 500 141 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [15/Jun/2023:22:27:37 +0000] "GET /assets/ HTTP/1.1" 404 185 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [15/Jun/2023:22:27:41 +0000] "GET /erpnext/ HTTP/1.1" 301 169 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [15/Jun/2023:22:27:47 +0000] "GET /index.html HTTP/1.1" 301 169 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [15/Jun/2023:22:28:03 +0000] "GET /index HTTP/1.1" 500 141 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [15/Jun/2023:22:28:04 +0000] "GET / HTTP/1.1" 500 141 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
127.0.0.1 - - [15/Jun/2023:22:28:23 +0000] "\x16\x03\x01\x02\x00\x01\x00\x01\xFC\x03\x03\xE0\xF3\xF3,\xC7\x85\x850m\x10jF\xB6\x99\xAF\x1FC-;\x88\xB1\x86\xBD\x16\xE5\xF3w" 400 157 "-" "-"
127.0.0.1 - - [15/Jun/2023:22:28:23 +0000] "\x16\x03\x01\x02\x00\x01\x00\x01\xFC\x03\x03\xEE\xCE\x96\xC0rG\xC0\xB3\x9D\xEF\xC2]{y\x15\xAF\x89nF0\x84=\x1A\xF2\xC4z\xBDr\x0ETX\xDD \x88zf\xDC\xC9\xE6o\x0B:\x95\xDBg\x221e\xB6\x8F\x8D\xB70\xFA\x83\x15\xA0j\x1A\xD1\x94\xDB\xCE\xEA\x19\x00 ::\x13\x01\x13\x02\x13\x03\xC0+\xC0/\xC0,\xC00\xCC\xA9\xCC\xA8\xC0\x13\xC0\x14\x00\x9C\x00\x9D\x00/\x005\x01\x00\x01\x93" 400 157 "-" "-"
127.0.0.1 - - [15/Jun/2023:22:28:23 +0000] "\x16\x03\x01\x02\x00\x01\x00\x01\xFC\x03\x03\xFA\xFA\xCBU\x97\xD3~\xF6\xAAJ\xC1F\xA1{\x9F`\x0B\xFA\xC2\xAB\xE2\xC1]6!(\xA8\x9F\xA6\xB0.O \x82l=M\x18%\xE7K\x87\x1E\x8A\x92?M\xADp\x8E\xFBo1\xD4Lg\xD6p(\xF4\xE6\xA0\x1F\xE3\x97\x00 jj\x13\x01\x13\x02\x13\x03\xC0+\xC0/\xC0,\xC00\xCC\xA9\xCC\xA8\xC0\x13\xC0\x14\x00\x9C\x00\x9D\x00/\x005\x01\x00\x01\x93zz\x00\x00\x00" 400 157 "-" "-"
127.0.0.1 - - [15/Jun/2023:22:28:23 +0000] "\x16\x03\x01\x02\x00\x01\x00\x01\xFC\x03\x03.\xD4\x8F\xB32\x5C%\xABu\x1C\xEF\x7F\x99\xFBC\xEBT\xDE`0^'\xDD\xAE\x1A1\xB6@@\xB4\x8B\xDF \x0Fy\xA0\x04\xFDu'\xBFIsZ\xBD\xB4\xBC\x866V\x87#\xBF" 400 157 "-" "-"

I pivotted and tried to use the Docker install

They speak about “Remote Containers” but the feature is now Dev Containers

/content/images/2023/06/frappe-05.png

But even following the guide crashed and didn’t work

/content/images/2023/06/frappe-06.png

Cloud hosting

I had spent far too much time trying to get this working. I finally pivoted to use the Cloud hosted

/content/images/2023/06/frappe-07.png

I could then login

/content/images/2023/06/frappe-08.png

We can now see some sites in the dashboard

/content/images/2023/06/frappe-09.png

We can see some details

/content/images/2023/06/frappe-10.png

I can login and set details

/content/images/2023/06/frappe-11.png

I then create a user account

/content/images/2023/06/frappe-12.png

Then we setup our Organization

/content/images/2023/06/frappe-13.png

And it then … ?fails

/content/images/2023/06/frappe-15.png

We can now login

/content/images/2023/06/frappe-14.png

it failed the first time, but on a second try

/content/images/2023/06/frappe-16.png

That kicked again the wizard

/content/images/2023/06/frappe-17.png

And upon a second attempt, this works

/content/images/2023/06/frappe-18.png

I’ll try adding a user

/content/images/2023/06/frappe-19.png

We can see some details and it claims a welcome email was sent

/content/images/2023/06/frappe-20.png

and I did find it in spam in my gmail

/content/images/2023/06/frappe-21.png

We can create an item to sell

/content/images/2023/06/frappe-22.png

and see it’s available in our items

/content/images/2023/06/frappe-23.png

I’ll stop here. I’m certain we can create customers and sales plans and reports. There are a lot of features throughout this app.

Summary

I fought to set this up in a cloud centric way. Kubernetes seemed to fail, even with a lot of cajoling. Docker didn’t work and even the docker instructions were out of date to VS Code terms which made me suspect.

I got the cloud version going, but it seems a tad buggy and I’m not sure I would be confident storing all my data in such a system.

The host FrappeCloud seems more about cloud hosting.

Even the FAQ suggests questions about ERPNext go to Erpnext.com and not them

/content/images/2023/06/frappe-24.png

But going there just links about to creating instances on frappecloud

/content/images/2023/06/frappe-25.png

If you read their full story here you see the founder, Rushabh Mehta basically created it for his families business and had it Open Sourced in 2009/2010.. But then pivoted to turn ERPNext into what he hopes is “like Wordpress like tool for ERPs”.

I might try again doing it in Kubernetes in the future. I think they really want to lean into being a SaaS. It might just be they don’t spend a lot of energy on kubernetes implementations as they are focused on their Cloud offering.

Frappe ERPNet Kubernetes

Have something to add? Feedback? Try our new forums

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes