dawarich on OpenShift

dawarich on OpenShift

Dienstag, Juni 10, 2025 dawarich

Installing dawarich on OpenShift

I was listening to a podcast from C't Raus aus der US-Cloud and there they mentioned the app dawarich which could even be hosted by yourself. So I thought I would give it try to run it on my OpenShift. The installation is documented only to run in a docker-compose environment or on a Synology NAS. So I had to do some tweaking which I wanted to share here (still some things to do but its quite ok for now).

Some preparation work

As it is running on OpenShift (opinionated implementation of K8S) there are some things that need to be done beforehand.

Create a project (aka namespace)

$ oc new-project dawarich

Create service account and set SCC

The containers run as root which per default does not work on OpenShift due to the Security Context Constraints (SCCs) (one of the todos:Running pods in Linux user namespaces ). To change the container to run with a different SCC, you must create a service account that is bound to a pod.

$ oc create sa dawarich-sa

To associate the service account with an SCC, use the oc adm policy command. Identify a service account by using the -z option.

$ oc adm policy add-scc-to-user anyuid -z dawarich-sa

Later on we need to set the service account to the deployments. But we need to create these first.

Persistent Storage

dawarich needs four pvcs:

  • dawarich_db_data ( dawarich_db Postgres)
  • dawarich_public (App)
  • dawarich_watched (App)
  • dawarich_storage (App)
  • dawarich_shared (redis)

Please create these four pvcs. dawarich_db_data and dawarich_shared need RWX storage.

Deploying the App

Create the Deployments

The dawarich app has 4 deployments:

  • dawarich-app (RoR)
  • dawarich-db (postgres)
  • dawarich-sidekiq sidekiq worker (for background jobs)
  • redis (redis database)

This ends up in two deployments:

dawarich database (postgres) deployment (based on image postgis/postgis:17-3.5-alpine)

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    alpha.image.policy.openshift.io/resolve-names: '*'
    app.openshift.io/route-disabled: "false"
  labels:
    app: dawarich-db
    app.kubernetes.io/component: dawarich-db
    app.kubernetes.io/instance: dawarich-db
    app.kubernetes.io/name: dawarich-db
    app.kubernetes.io/part-of: dawarich_redis
    app.openshift.io/runtime-namespace: dawarich
  name: dawarich-db
  namespace: dawarich
  uid: 6ab3d44a-6fce-48c3-b82f-7359b68171c5
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: dawarich-db
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: dawarich-db
        deployment: dawarich-db
    spec:
      containers:
      - env:
        - name: DATABASE_USERNAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_USERNAME
        - name: DATABASE_PASSWORD
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_PASSWORD
        - name: DATABASE_NAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_NAME
        - name: DATABASE_HOST
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_HOST
        image: postgis/postgis:17-3.5-alpine
        imagePullPolicy: IfNotPresent
        name: dawarich-db
        ports:
        - containerPort: 5432
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/lib/postgresql/data
          name: postgres-darwarich-data
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: dawarich-sa
      serviceAccountName: dawarich-sa
      terminationGracePeriodSeconds: 30
      volumes:
      - name: postgres-darwarich-data
        persistentVolumeClaim:
          claimName: postgres-darwarich-data-new

The second deployment for the actual app (RoR):

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    alpha.image.policy.openshift.io/resolve-names: '*'
    app.openshift.io/route-disabled: "false"
  labels:
    app: dawarich-app
    app.kubernetes.io/component: dawarich-app
    app.kubernetes.io/instance: dawarich-app
    app.kubernetes.io/name: dawarich-app
    app.openshift.io/runtime-namespace: dawarich
  name: dawarich-app
  namespace: dawarich
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: dawarich-app
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: dawarich-app
        deployment: dawarich-app
    spec:
      restartPolicy: Always
      initContainers:
      - name: db-migrate
        command: [ "bin/rails", "db:migrate" ]
        image: freikin/dawarich:latest
        imagePullPolicy: IfNotPresent
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        env:
        - name: DATABASE_USERNAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_USERNAME
        - name: DATABASE_PASSWORD
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_PASSWORD
        - name: DATABASE_NAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_NAME
        - name: DATABASE_HOST
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_HOST
      - name: data-migrate
        command: [ "bin/rails", "data:migrate" ]
        image: freikin/dawarich:latest
        imagePullPolicy: IfNotPresent
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        env:
        - name: DATABASE_USERNAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_USERNAME
        - name: DATABASE_PASSWORD
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_PASSWORD
        - name: DATABASE_NAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_NAME
        - name: DATABASE_HOST
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_HOST
      containers:
      - name: dawarich-app
        args:
        - server
        - -p
        - "3000"
        - -b
        - '::'
        command:
        - bin/rails
        env:
        - name: DATABASE_USERNAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_USERNAME
        - name: DATABASE_PASSWORD
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_PASSWORD
        - name: DATABASE_NAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_NAME
        - name: MIN_MINUTES_SPENT_IN_CITY
          value: "60"
        - name: APPLICATION_HOSTS
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: APPLICATION_HOSTS
        - name: TIME_ZONE
          value: Europe/Zurich
        - name: APPLICATION_PROTOCOL
          value: http
        - name: DISTANCE_UNIT
          value: km
        - name: ROMETHEUS_EXPORTER_ENABLED
          value: "false"
        - name: SELF_HOSTED
          value: "true"
        - name: DATABASE_HOST
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_HOST
        - name: REDIS_URL
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: REDIS_URL
        - name: RAILS_ENV
          value: development
        - name: STORE_GEODATA
          value: "true"
        image: freikin/dawarich:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3000
          protocol: TCP
        readinessProbe:
          failureThreshold: 30
          httpGet:
            path: /api/v1/health
            port: 3000
            scheme: HTTP
          initialDelaySeconds: 30
          periodSeconds: 20
          successThreshold: 3
          timeoutSeconds: 10
        resources:
          limits:
            cpu: "1"
            memory: 3Gi
          requests:
            cpu: 500m
            memory: 2Gi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/app/public
          name: dawarich-public
        - mountPath: /var/app/tmp/imports/watched
          name: dawarich-watched
        - mountPath: /var/app/storage
          name: dawarich-storage
        - mountPath: /dawarich_db_data
          name: postgres-darwarich-data-new
      dnsPolicy: ClusterFirst
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: dawarich-sa <------------------- service account we created earlier
      serviceAccountName: dawarich-sa
      terminationGracePeriodSeconds: 30
      volumes:
      - name: dawarich-public
        persistentVolumeClaim:
          claimName: dawarich-public
      - name: dawarich-watched
        persistentVolumeClaim:
          claimName: dawarich-watched
      - name: dawarich-storage
        persistentVolumeClaim:
          claimName: dawarich-storage

The redis deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    alpha.image.policy.openshift.io/resolve-names: '*'
    app.openshift.io/route-disabled: "false"
  labels:
    app: redis
    app.kubernetes.io/component: redis
    app.kubernetes.io/instance: redis
    app.kubernetes.io/name: redis
    app.kubernetes.io/part-of: dawarich_redis
    app.openshift.io/runtime-namespace: dawarich
  name: redis
  namespace: dawarich
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: redis
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      annotations:
      labels:
        app: redis
        deployment: redis
    spec:
      containers:
      - image: redis:7.4-alpine
        imagePullPolicy: IfNotPresent
        name: redis
        ports:
        - containerPort: 6379
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /data
          name: dawarich-db
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
      volumes:
      - persistentVolumeClaim:
          claimName: dawarich-pg-shared
        name: dawarich-db

Aaaaaand finally the sidekiq deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    alpha.image.policy.openshift.io/resolve-names: '*'
    app.openshift.io/route-disabled: "false"
  labels:
    app: dawarich-sidekiq
    app.kubernetes.io/component: dawarich-sidekiq
    app.kubernetes.io/instance: dawarich-sidekiq
    app.kubernetes.io/name: dawarich-sidekiq
    app.kubernetes.io/part-of: dawarich_redis
    app.openshift.io/runtime-namespace: dawarich
  name: dawarich-sidekiq
  namespace: dawarich
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: dawarich-sidekiq
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: dawarich-sidekiq
        deployment: dawarich-sidekiq
    spec:
      containers:
      - args:
        - exec
        - sidekiq
        command:
        - bundle
        env:
        - name: RAILS_ENV
          value: development
        - name: REDIS_URL
          valueFrom:
            secretKeyRef:
              name: dawarich-app-env
              key: REDIS_URL
        - name: DATABASE_USERNAME
          valueFrom:
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_USERNAME
        - name: DATABASE_HOST
          valueFrom:
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_HOST
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_PASSWORD
        - name: DATABASE_NAME
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: DATABASE_NAME
        - name: APPLICATION_HOSTS
          valueFrom: 
            secretKeyRef:
              name: dawarich-app-env
              key: APPLICATION_HOSTS
        - name: BACKGROUND_PROCESSING_CONCURRENCY
          value: "10"
        - name: APPLICATION_PROTOCOL
          value: http
        - name: DISTANCE_UNIT
          value: km
        - name: PROMETHEUS_EXPORTER_ENABLED
          value: "false"
        - name: PROMETHEUS_EXPORTER_HOST
          value: dawarich_app
        - name: PROMETHEUS_EXPORTER_PORT
          value: "9394"
        - name: ENABLE_TELEMETRY
          value: "false"
        - name: SELF_HOSTED
          value: "true"
        image: freikin/dawarich:latest
        imagePullPolicy: Always
        name: dawarich-sidekiq
        ports:
        - containerPort: 3000
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: dawarich-sa
      serviceAccountName: dawarich-sa
      terminationGracePeriodSeconds: 30

Considerations:

APPLICATION_HOSTS:

if you are running it behind a reverse proxy you need to adapt the FQDN accordingly. See Setting up reverse proxy

DATABASE_HOST:

You need to create a internal service so that the app is able to connect to it:

apiVersion: v1
kind: Service
metadata:
  annotations:
  labels:
    app: dawarich-db
    app.kubernetes.io/component: dawarich-db
    app.kubernetes.io/instance: dawarich-db
  name: data base service name
  namespace: dawarich
spec:
  clusterIP: 172.30.219.230
  clusterIPs:
  - 172.30.219.230
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - name: 5432-tcp
    port: 5432
    protocol: TCP
    targetPort: 5432
  selector:
    app: dawarich-db
    deployment: dawarich-db
  sessionAffinity: None
  type: ClusterIP

REDIS_URL

You need to create a internal service to be able to connect to the redis service:

kind: Service
apiVersion: v1
metadata:
  name: redis service name
  namespace: dawarich
spec:
  clusterIP: 172.30.141.216
  ipFamilies:
    - IPv4
  ports:
    - protocol: TCP
      port: 6379
      targetPort: 6379
  internalTrafficPolicy: Cluster
  clusterIPs:
    - 172.30.141.216
  type: ClusterIP
  ipFamilyPolicy: SingleStack
  sessionAffinity: None
  selector:
    app: redis

Also you would need a secret with all the env values you do not want to expose:

kind: Secret
apiVersion: v1
metadata:
  name: dawarich-app-env
  namespace: dawarich
data:
  APPLICATION_HOSTS: your.fqdn.xyz
  DATABASE_HOST: service to the db
  DATABASE_NAME: database name
  DATABASE_PASSWORD: database pw
  DATABASE_USERNAME: username for db
  REDIS_URL: redis service 

As the original setup from dawarich relies on docker compose and the app is meant for development purposes there are some things need to be done manually.

    spec:
      containers:
      - args:
        - server
        - -p
        - "3000"
        - -b
        - '::'
        command:
        - bin/rails

We need to add the above arguments (see deployment dawarich-app). With this we override the original startup commands and arguments from the docker compose file. There you see that the entrypoint and the command. The entrypoint is always executed before the actual startup. This is not really a problem until you look at the part of the script:

# Run primary database migrations first (needed before other migrations)
echo "Running primary database migrations..."
bundle exec rails db:migrate

# Run data migrations
echo "Running DATA migrations..."
bundle exec rake data:migrate

#Demo user and password
echo "Running seeds..."
bundle exec rails db:seed

This always creates the demo user and you do not want that. For the initial startup you would need that in order to log in. But later on you want to get rid of it.

So at the initial startup you would need to login to the container and run the script:

# oc rsh dawarich-app-your-pod-id
# cd /usr/local/bin
# ls
bundle  bundler  erb  gem  irb  racc  rake  rbs  rdbg  rdoc  ri  ruby  syntax_suggest  typeprof  web-entrypoint.sh
# ./web-entrypoint.sh
# exit

Now you should restart the app pod and then everything should be ready and working. As we have the initContainers in the dawarich-app deployments the db and data migrations being executed at every start. So we should be fine that.

Zu diesem Artikel gibt es noch keine Kommentare

Blog

blogs
Suchen