A logo showing the text blog.marcnuri.com
Español
Home»Pet projects»Isotope Mail: How to deploy Isotope+Traefik into Kubernetes

Recent Posts

  • Fabric8 Kubernetes Client 7.4 is now available!
  • Kubernetes MCP Server Joins the Containers Organization!
  • MCP Tool Annotations: Adding Metadata and Context to Your AI Tools
  • Fabric8 Kubernetes Client 7.2 is now available!
  • Connecting to an MCP Server from JavaScript using AI SDK

Categories

  • Artificial Intelligence
  • Front-end
  • Go
  • Industry and business
  • Java
  • JavaScript
  • Legacy
  • Operations
  • Personal
  • Pet projects
  • Tools

Archives

  • September 2025
  • July 2025
  • May 2025
  • April 2025
  • March 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • August 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023
  • March 2023
  • February 2023
  • January 2023
  • December 2022
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • July 2022
  • June 2022
  • May 2022
  • March 2022
  • February 2022
  • January 2022
  • December 2021
  • November 2021
  • October 2021
  • September 2021
  • August 2021
  • July 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • February 2020
  • January 2020
  • December 2019
  • October 2019
  • September 2019
  • July 2019
  • March 2019
  • November 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • December 2017
  • October 2017
  • August 2017
  • July 2017
  • January 2017
  • December 2015
  • November 2015
  • December 2014
  • March 2014
  • February 2011
  • November 2008
  • June 2008
  • May 2008
  • April 2008
  • January 2008
  • November 2007
  • September 2007
  • August 2007
  • July 2007
  • June 2007
  • May 2007
  • April 2007
  • March 2007

Isotope Mail: How to deploy Isotope+Traefik into Kubernetes

2019-10-10 in Pet projects / Operations / Legacy tagged Containers / DevOps / Docker / E-Mail / Isotope Mail / Kubernetes / Mail / Traefik / Webmail by Marc Nuri | Last updated: 2021-11-30
Versión en Español

Introduction

Isotope mail client is a free open source webmail application and one of the side projects in which I invested my spare time during the last year. You can read more about Isotope's features in a previous blog post.

Although there is still no official release, the application is quite stable and usable. In this post, I will show you how to deploy the application to a Kubernetes cluster. For the purpose of the tutorial I've used minikube + kubectl, but the same steps should be reproducible in a real K8s cluster.

Traefik v1

Despite it's not part of the implementation, Traefik (or any other alternative) is one of the main pieces of the deployment as it will act as the API gateway (reverse-proxy) and route the requests to the appropriate Isotope component/service.

The first step is to create an Ingress controller for Traefik (if there isn't any in the cluster yet). We will follow Traefik's official documentation to create the new Ingress controller.

Role Based Access Control configuration (Kubernetes 1.6+ only)

Starting from 1.6 Kubernetes has introduced Role Based Access Control (RBAC) to allow more granular access control to resources based on the roles of individual users.

In order to allow Traefik to access global Kubernetes API, it's necessary to create a ClusterRole and a ClusterRoleBinding.

traefik.rbac.yaml
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller
  namespace: kube-system

traefik.rbac.yaml

bash
kubectl apply -f https://raw.githubusercontent.com/manusa/isotope-mail/master/deployment-examples/k8s/traefik.rbac.yaml
Kubectl Isotope Traefik RBAC
Kubectl Isotope Traefik RBAC

Deploy Traefik using a DaemonSet

The next step is to deploy Traefik ingress controller using a DaemonSet.

If you follow Traefik's official documentation, you'll see you can also achieve this using a Deployment. For the sake of this tutorial and as we are using Minikube, we will use a DaemonSet because it's easier to configure and to expose with Minikube (Minikube has problems to assign external IPs to ingresses using Traefik ingress controller deployed using a Deployment).

traefik.ds.yaml
---
kind: DaemonSet
apiVersion: apps/v1
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
  labels:
    k8s-app: traefik-ingress-lb
spec:
  selector:
    matchLabels:
      k8s-app: traefik-ingress-lb
  template:
    metadata:
      labels:
        k8s-app: traefik-ingress-lb
        name: traefik-ingress-lb
    spec:
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 60
      containers:
        - image: traefik:v1.7
          name: traefik-ingress-lb
          ports:
            - name: http
              containerPort: 80
              hostPort: 80
            - name: admin
              containerPort: 8080
              hostPort: 8080
          securityContext:
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
          args:
            - --api
            - --kubernetes
            - --logLevel=INFO
---
kind: Service
apiVersion: v1
metadata:
  name: traefik-ingress-service
  namespace: kube-system
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
    - protocol: TCP
      port: 80
      name: web
    - protocol: TCP
      port: 8080
      name: admin

traefik.ds.yaml

bash
kubectl apply -f https://raw.githubusercontent.com/manusa/isotope-mail/master/deployment-examples/k8s/traefik.ds.yaml
Kubectl Isotope Traefik DaemonSet
Kubectl Isotope Traefik DaemonSet

Traefik UI

We can optionally create a Service and Ingress for the Traefik web UI dashboard in order to monitor the new Traefik DaemonSet deployment.

traefik.ui.yaml
---
apiVersion: v1
kind: Service
metadata:
  name: traefik-web-ui
  namespace: kube-system
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
    - name: web
      port: 80
      targetPort: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: traefik-web-ui
  namespace: kube-system
spec:
  rules:
    - host: traefik-ui.minikube
      http:
        paths:
          - path: /
            backend:
              serviceName: traefik-web-ui
              servicePort: web

traefik.ui.yaml

bash
kubectl apply -f https://raw.githubusercontent.com/manusa/isotope-mail/master/deployment-examples/k8s/traefik.ui.yaml
Kubectl Isotope Traefik web UI
Kubectl Isotope Traefik web UI

It's important to note that for the Ingress configuration we are using traefik-ui.minikube as the public host. In a production environment, we would add a real hostname. An additional required step is to add the Kubernetes cluster IP (minikube ip / kubectl get ingress) into our local hosts file (/etc/hosts).

Finally, we can point our browser to traefik-ui.minikube to load Traefik's dashboard.

Traefik Web UI Dashboard
Traefik Web UI Dashboard

Isotope

The last step is to deploy Isotope.

isotope.yaml
---
apiVersion: v1
kind: Secret
metadata:
  name: isotope-secrets
type: Opaque
data:
  encryptionPassword: U2VjcmV0SzhzUGFzc29yZA==
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: isotope-server
  labels:
    app: isotope
    component: server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: isotope
      component: server
      version: latest
  template:
    metadata:
      labels:
        app: isotope
        component: server
        version: latest
    spec:
      containers:
        - name: isotope-server
          image: marcnuri/isotope:server-latest
          imagePullPolicy: Always
          ports:
            - containerPort: 9010
          env:
            - name: ENCRYPTION_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: isotope-secrets
                  key: encryptionPassword
          livenessProbe:
            httpGet:
              path: /actuator/health
              port: 9010
            failureThreshold: 6
            periodSeconds: 5
          # Use startupProbe instead if your k8s version supports it
            initialDelaySeconds: 60
          readinessProbe:
            httpGet:
              path: /actuator/health
              port: 9010
            failureThreshold: 2
            periodSeconds: 5
#          startupProbe:
#            httpGet:
#              path: /actuator/health
#              port: 9010
#            initialDelaySeconds: 20
#            failureThreshold: 15
#            periodSeconds: 10
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: isotope-client
  labels:
    app: isotope
    component: client
spec:
  replicas: 1
  selector:
    matchLabels:
      app: isotope
      component: client
      version: latest
  template:
    metadata:
      labels:
        app: isotope
        component: client
        version: latest
    spec:
      containers:
        - name: isotope-client
          image: marcnuri/isotope:client-latest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          livenessProbe:
            httpGet:
              path: /favicon.ico
              port: 80
            failureThreshold: 6
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: isotope-server
spec:
  ports:
    - name: http
      targetPort: 9010
      port: 80
  selector:
    app: isotope
    component: server
---
apiVersion: v1
kind: Service
metadata:
  name: isotope-client
spec:
  ports:
    - name: http
      targetPort: 80
      port: 80
  selector:
    app: isotope
    component: client
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: isotope
  annotations:
    kubernetes.io/ingress.class: traefik
    traefik.frontend.rule.type: PathPrefixStrip
spec:
  rules:
    - host: isotope.minikube
      http:
        paths:
          - path: /
            backend:
              serviceName: isotope-client
              servicePort: http
          - path: /api
            backend:
              serviceName: isotope-server
              servicePort: http

isotope.yaml

bash
kubectl apply -f https://raw.githubusercontent.com/manusa/isotope-mail/master/deployment-examples/k8s/isotope.yaml
Kubectl Isotope Mail Client
Kubectl Isotope Mail Client

Secret

The first entry in the Yaml configuration is a Kubernetes Base64 encoded secret that will be used to set the encryption symmetric key in Isotope Server component.

Isotope Server Deployment

The next entry is the Deployment configuration for the Isotope Server component. As this deployment is more complex than the Client Deployment, basic common configurations for both components will be described in the next section (Isotope Client Deployment).

In the env section, we're declaring the ENCRYPTION_PASSWORD environment variable and assigning it the value of the Secret we declared in the previous step. This variable will be available to all Pods created by Kubernetes from this Deployment configuration, thus, all Pods will share the same encryption key and will be compatible.

We're also defining two different probes so that Traefik and Kubernetes know when the Isotope Server Pods are ready and traffic can be routed to them. Liveness probe will be used to determine if the container is still alive. Otherwise, Kubernetes will restart the Pod as the application state is considered to be broken. We're also using the initialDelaySeconds because the application takes a couple of seconds to spin up and this way we'll avoid false positives for the probe. If your Kubernetes version supports it, it's better to define a startup probe instead of this initialDelaySeconds.

A readiness probe is also defined in this section. This probe is similar to the liveness probe and will be used to indicate if the application is ready to receive traffic. If for some reason the application is temporarily not admitting traffic (max number of connections, etc.) the probe will set the application "down" but Kubernetes will not restart it.

For both probes, we're using an HTTP request pointing to Spring Actuator's health check endpoint which is available in Isotope Server component.

Isotope Client Deployment

Same as we did for the Server component, we're now defining a Deployment for Isotope's Client component.

In the spec section we're defining the number of replicas we want for our Pods, in this case, one. The selector property, although optional in previous API versions, is now mandatory and will be used by Kuberentes to determine the number of Pods actually running to spin up more replicas if necessary (they should match the labels in the template section).

The template entry within the spec section is used to define the Pod specifications. For Isotope Client we're defining a Pod with a single container based on marcnuri/isotope:client-latest Docker image exposing Http port.

As with the Isotope Server deployment, we define a simple liveness probe in case the Pod becomes unstable and reaches a broken state so that Kubernetes will automatically restart it.

Services

The next section in the configuration defines a Service for each of the previous deployments (server/client) in order to expose them to the cluster.

To ease the Ingress definition in further steps, both Services will expose Http port (80).

Ingress

The final section of the configuration defines an Ingress using the Ingress controller deployed in the first steps of the tutorial.

We are going to use isotope.minikube as the public host, although in a production environment we should use a valid and real hostname. We will also need to add an additional entry in our /etc/hosts file.

Traffic reaching http://isotope.minikube/api will be routed by Traefik to isotope-server service, traffic reaching http://isotope.minikube/ will be routed to isotope-client service.

The use of traefik.frontend.rule.type: PathPrefixStrip configuration will remove /api from the requests to isotope-server service, this way, no additional modifications or configurations will be necessary for Isotope Server component to be compatible with our deployment.

Traefik dashboard with Isotope

Once Isotope configuration is deployed, Traefik dashboard will automatically update and display the new routes for Isotope.

Traefik Dashboard with Isotope
Traefik Dashboard with Isotope

Isotope deployment

If everything went OK and Traefik dashboards display healthy Isotope components, we can now point our browser to http://isotope.minikube where Isotope will be ready and accessible.

Isotope in Kubernetes
Isotope in Kubernetes
Isotope + Kubernetes
Isotope + Kubernetes
Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Post navigation
Quarkus + Fabric8 Maven Plugin + GraalVM integrationFedora: How to install Cinnamon desktop environment
© 2007 - 2025 Marc Nuri