Use Docker with Minikube

Last modified: February 24, 2024

1 Introduction

This how-to takes you through the process of deploying a Docker image of your Mendix app to Minikube, a local version of Kubernetes which runs in a Windows container or virtual machine. Many of the operations you perform on Minikube are the same as those on a hosted environment and it provides a low-level entry to Kubernetes. For more information, see Installing Kubernetes with Minikube on the Kubernetes documentation site.

Kubernetes is a standard container orchestration platform supported by Mendix. For details on supported version of Kubernetes see Mendix System Requirements. When publishing to your cloud infrastructure, Mendix suggests you use Mendix for Private Cloud to deploy Mendix apps to Kubernetes as this provides you with integration with the Developer Portal and takes away much of the heavy lifting.

This how-to teaches you how to do the following:

  • Deploy and run a Mendix app on Kubernetes using Minikube
  • Separate the database deployment from your app
  • Attach persistence storage to the app container

2 Prerequisites

To follow this how-to, you should have a basic knowledge of Docker and Kubernetes. For more information, see Docker Overview and Kubernetes Basics. Although you do not need more knowledge to execute all the commands provided, some experience will help you to understand the how-to better.

Before starting this how-to, make sure you have completed the following prerequisites:

  • Install kubectl using the instructions provided in Install and Set Up kubectl
    • The kubectl CLI is the default tool to access and manage your Kubernetes cluster
  • Install Minikube using the instructions provided in Install Minikube
    • Minikube allows you to create a local cluster that is convenient for exploring Kubernetes (if you have an account for one of the cloud providers and you choose to use that, this step can be skipped)
  • Build your image in Minikube, using the steps in the Docker page and replacing Docker commands such as docker build with Minikube equivalents such as minikube image build.

This how-to uses commands for a Unix-like system. The commands for Windows may be slightly different.

3 Architecture Overview

Before you start, here is some background information on the components needed to deploy a Mendix app on Kubernetes.

A Mendix application needs, as a minimum, a database to run. In this example you provision a PostgreSQL database within the Kubernetes cluster. In production scenarios, the database is usually provided as a service by the cloud provider, like AWS RDS or Azure SQL. For supported databases see Mendix System Requirements.

If the application makes use of persistable FileDocument or FileImage entities, a persistent volume (PV) storage service needs to be attached as well. See Mendix System Requirements for supported external storage services. In this how-to you use a node-bound storage volume as an example. For more information, see Architecture Overview, below.

This architecture overview shows all the components in the deployment:

The deployment of your Mendix app needs the following Kubernetes components:

The database is deployed as a deployment. Deployment covers control over the pods and the ReplicaSets for these pods. Pods are not bound to a specific node in the cluster unless set with selector labels. A deployment can scale pods on one or mode nodes, and it recovers pods when they crash.

The Mendix application is deployed using a StatefulSet. The StatefulSet generally provides the same control options as a deployment, but it will provide a stable index number for the pod as well as a network ID and storage. The StatefulSet is used to provide the application with a unique pod index number that identifies which of the instances is allowed to run scheduled events.

Data storage should be externalized as much as possible, because pods can be created, destroyed, or moved around. Destroying a pod will also destroy any data stored inside the containers started by the pod. When scaling the app, all instances should be able to retrieve the same data. This how-to uses node-bound volume mounts, but please check the list of available clustered storage options.

To access your Mendix applications inside a pod from outside of the Kubernetes, a service must be created to expose the port. Services deal with pod discovery and pod lifecycles, so the consumer of a particular service doesn’t need to know where a pod is or what IP is needed to access it.

4 Deploying the Components

4.1 Deploying the PostgreSQL Database

Once Minikube is running you must configure your local environment to use the Docker daemon using the following command:

minikube docker-env

You must first deploy your database. Minikube uses an external folder to persist the data outside of the database pod.

Here is the definition of the postgres-deployment.yaml database component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:11
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_DB
              value: db0
            - name: POSTGRES_USER
              value: mendix
            - name: POSTGRES_PASSWORD
              value: mendix
          volumeMounts:
            - mountPath: "/var/lib/postgresql/data"
              name: "mendix-pgdata"
      volumes:
        - hostPath:
            path: "/home/docker/pgdata"
          name: mendix-pgdata

To create the PostgreSQL database, Mendix uses the provided postgres image. The environment variables provided in env are needed to configure the default database. Instead of setting the password in the yaml file directly, you can choose to use secrets.

And finally, it is necessary to expose the database as a service and make it available to the application. This is the definition of such a service:

(postgres-service.yaml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
spec:
  type: ClusterIP
  ports:
    - port: 5432
  selector:
    app: postgres

To create all the mentioned components, use the following:

1
2
kubectl create -f postgres-deployment.yaml
kubectl create -f postgres-service.yaml

The database is now created. To verify the installation, check out the logs:

kubectl logs $(kubectl get pods -lapp=postgres -o name)

The output should be similar to the following:

2017-09-14 08:34:37.538 UTC [1] LOG: database system is ready to accept connections

The host and port values will be needed to deploy the application. To get these we execute the following command:

kubectl get service postgres-service

If you are using Windows, you need to execute these inline commands first to get the pod name:

kubectl get pods -lapp=postgres -o name

then use the pod name to retrieve the logs:

kubectl logs <name>

4.2 Adding a Persistent Volume

The Docker Buildpack stores files in /opt/mendix/build/data/files. If you do not have any persistent storage, then these files will disappear if the pod is destroyed. If you mount a Persistent Volume (PV) into this path, any uploaded files will be stored in that PV.

To do this, you need to ensure that the volumeMounts parameter in the mendixapp.yaml file points to /opt/mendix/build/data/files. This is already set up in the sample file in Deploying the Application, below

4.3 Deploying the Application

With the database running, we can deploy our application. You will use a sample Docker container with a Mendix app published in hub.docker.com. To create a new Docker container for your Mendix app, see the description on the docker-mendix-buildpack.

Before deploying the app, you need to create some secrets so that sensitive information for the application doesn’t need to be in the yaml file. The secrets file is applied once to the cluster, and the values will be kept there. For information on all the options, see Secrets.

Create a file mendix-app-secrets.yaml with the following contents:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
apiVersion: v1
kind: Secret
metadata:
  name: mendix-app-secrets
type: Opaque
data:
  admin-password: YOUR_ADMIN_PASSWORD
  db-endpoint: YOUR_DATABASE_ENDPOINT
  license-key: YOUR_LICENSE_KEY
  license-id: YOUR_LICENSE_ID

YOUR-DATABASE-ENDPOINT is in the form postgres://mendix:mendix@255.255.255.255:5432/db0 (for example, postgres://mendix:mendix@172.17.0.3:5432/db0). You can find the correct IP address and port for your database endpoint using the following command:

kubectl get ep postgres-service

See Run a Mendix Docker Image for expected value formats.

Create the secrets in Kubernetes by executing the following command:

kubectl create -f mendix-app-secrets.yaml

Once the database service and the secrets are created, you can create the application, which is defined in the file below.

mendix-app.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mendix-k8s-stateful
  labels:
    app: mendix-k8s
spec:
  serviceName: mendix-app-service
  replicas: 2
  selector:
    matchLabels:
      app: mendix-k8s
  template:
    metadata:
      labels:
        app: mendix-k8s
    spec:
      containers:
        - name: mendix-app
          image: <hub-user>/<repo-name>:<tag>
          imagePullPolicy: Always
          ports:
            - containerPort: 8080 
          volumeMounts:
            - mountPath: "/opt/mendix/build/data/files"
              subPath: files
              name: mendix-data
          env:
            - name: ADMIN_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mendix-app-secrets
                  key: admin-password
            - name: DATABASE_ENDPOINT
              valueFrom:
                secretKeyRef:
                  name: mendix-app-secrets
                  key: db-endpoint
            - name: LICENSE_ID
              valueFrom:
                secretKeyRef:
                  name: mendix-app-secrets
                  key: license-id
            - name: LICENSE_KEY
              valueFrom:
                secretKeyRef:
                  name: mendix-app-secrets
                  key: license-key      
      volumes:
        - hostPath:
            path: "/home/docker/mendix-files"
          name: mendix-data

Replace <hub-user>/<repo-name>:<tag> with the Docker image of your app, for example, mendix/sample-app-kubernetes:v3.

Create a Docker image of your Mendix app using the instructions in the Mendix Docker Buildpack on GitHub.

Once you have created the Docker image, push it to the Docker hub using the following command:

minikube image push <hub-user>/<repo-name>:<tag>

Where <hub-user>/<repo-name>:<tag> is the Docker image of your app identified in mendix-app.yaml. For the example above, this is again mendix/sample-app-kubernetes:v3.

Deploy the application to Kubernetes:

kubectl create -f mendix-app.yaml

4.3.1 Some Notes on Scaling

The Mendix runtime is stateless, meaning that a client can talk to any server instance. However, scheduled events and database migrations should be handled by only one instance. This is done using a container index count. The pod with index 0 will always trigger the schedule events and deal with database updates in case of an upgrade version.

Setting kind: StatefulSet rather than kind: Deployment appends a container instance index to the container’s hostname.

It should be noted that using a StatefulSet versus a deployment involves some difference in behavior. For example, a pod won’t move to a different node when it crashes, and when the node is not reachable, the pod is not recreated on another system.

4.4 Making the App Available

To make the app available from the browser, it needs to be accessible outside of the cluster. For this, Mendix uses a service of the LoadBalancer or NodePort type. For Minikube we can use both, which exposes the app via an IP address.

If you deploy to a cloud provider, the method for publishing your app may be different (for example, some cloud providers can automatically update the load balancer to forward a URL request to the cluster). For more information, see Create an External Load Balancer.

The definition of publishing the Mendix app as a NodePort service is described in the following file:

mendix-app-service.yaml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
  name: mendix-app-service
  labels:
    app: mendix-k8s
spec:
  ports:
  - port: 8080
    protocol: TCP
  selector:
    app: mendix-k8s
  type: NodePort

Deploy the service:

kubectl create -f mendix-app-service.yaml

Check if the application is running using the command:

minikube service mendix-app-service

To get the URL to the application on Minikube, execute this command and open the link in your browser:

minikube service mendix-app-service --url

Congratulations! You have deployed your first Mendix app in Kubernetes.

5 Read More