Dashy is a highly customizable dashboard app which makes a great homepage for accessing all of your personal applications. Out of the box, it doesn’t provide any authentication or authorization so anyone with access will see the dashboard. I am using Authelia to protect it. The Dashy configuration is written in yaml so it’s a perfect candidate for a kubernetes ConfigMap.

Dashy Role

Add the Dashy variables to inventory/group_vars/k3s_cluster:

dashy_namespace: dashy
dashy_image: lissy93/dashy:2.1.1
dashy_hostname: dashy.domain.tld

roles/k3s_cluster/dasy/tasks/main.yml:

- name: Dashy Namespace
  kubernetes.core.k8s:
    kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
    state: present
    definition: "{{ lookup('template', 'manifests/namespace.j2') }}"
    validate:
      fail_on_error: yes
  delegate_to: "{{ ansible_host }}"
  run_once: true
- name: Dashy Deployment
  kubernetes.core.k8s:
    kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
    state: present
    definition: "{{ lookup('template', 'manifests/deployment.j2') }}"
    validate:
      fail_on_error: yes
  delegate_to: "{{ ansible_host }}"
  run_once: true
- name: Dashy ConfigMap
  kubernetes.core.k8s:
    kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
    state: present
    definition: "{{ lookup('template', 'manifests/configmap.j2') }}"
    validate:
      fail_on_error: yes
  delegate_to: "{{ ansible_host }}"
  run_once: true
- name: Dashy Service
  kubernetes.core.k8s:
    kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
    state: present
    definition: "{{ lookup('template', 'manifests/service.j2') }}"
    validate:
      fail_on_error: yes
  delegate_to: "{{ ansible_host }}"
  run_once: true
- name: Dashy Ingress
  kubernetes.core.k8s:
    kubeconfig: "/var/lib/rancher/k3s/server/cred/admin.kubeconfig"
    state: present
    definition: "{{ lookup('template', 'manifests/ingress.j2') }}"
    validate:
      fail_on_error: yes
  delegate_to: "{{ ansible_host }}"
  run_once: true

roles/k3s_cluster/dashy/manifests/namespace.j2:

apiVersion: v1
kind: Namespace
metadata:
  name: {{ dashy_namespace }}

roles/k3s_cluster/dashy/manifests/deployment.j2:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dashy
  namespace: {{ dashy_namespace }}
  labels:
    app: dashy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dashy
  template:
    metadata:
      labels:
        app: dashy
    spec:
      containers:
        - name: dashy
          env:
          - name: PORT
            value: "80"
          image: {{ dashy_image }}
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
          volumeMounts:
            - name: dashy-config
              mountPath: /app/public
      volumes:
        - name: dashy-config
          configMap:
            name: dashy

roles/k3s_cluster/dashy/manifests/configmap.j2:

apiVersion: v1
data:
  conf.yml: |-
    pageInfo:  
      title: My Dashboard
    sections: 
    # https://dashy.to/docs/icons/
      - name: Applications
        icon: fas fa-browser
        items:
          - title: Nextcloud
            icon: hl-Nextcloud
            url: https://{{ nextcloud_hostname }}
      - name: Authentication
        icon: fas fa-lock
        items:
          - title: Authelia
            icon: hl-Authelia
            url: https://{{ authelia_hostname }}
          - title: Vaultwarden
            icon: hl-bitwarden
            url: https://{{ vaultwarden_hostname }}
      - name: Development Tools
        icon: fas fa-brackets-curly
        items:
          - title: Gitea
            icon: hl-gitea
            url: https://{{ gitea_hostname }}
          - title: Drone
            icon: hl-drone-blue
            url: https://{{ drone_hostname }}
      - name: Kubernetes
        icon: hl-kubernetes-dashboard
        items:
          - title: Kubernetes Dashboard
            icon: hl-kubernetes-dashboard
            url: https://{{ dashboard_hostname }}
          - title: Traefik Dashboard
            icon: hl-traefik
            url: https://{{ traefik_hostname }}/dashboard/
          - title: Longhorn Dashboard
            icon: hl-longhorn
            url: https://{{ longhorn_hostname }}
      - name: Lachlan Life 
        icon: fas fa-rss
        items:
          - title: Lachlan Life Staging
            icon: fas fa-flask-vial
            url: https://{{ lachlanlife_staging_hostname }} 
          - title: Lachlan Life
            icon: hl-hugo
            url: https://{{ lachlanlife_hostname }} 
      - name: Messaging
        icon: fas fa-comments
        items:
          - title: Element (Matrix Client)
            icon: hl-Element
            url: https://app.element.io
      - name: Monitoring
        icon: fas fa-computer
        items:
          - title: Grafana
            icon: hl-grafana
            url: https://{{ grafana_hostname }}
          - title: Prometheus
            icon: hl-prometheus
            url: https://{{ prometheus_hostname }}    
      - name: Notifications
        icon: fas fa-wave-square
        items:
          - title: Ntfy
            icon: fas fa-bells
            url: https://{{ ntfy_hostname }}
          - title: Alert Manager
            icon: hl-alertmanager
            url: https://{{ alertmanager_hostname }}
      - name: Dashy
        icon: far fa-rocket  
        items:  
          - title: Dashy Repository
            description: Dashy source code and docs
            icon: fab fa-github
            url: https://github.com/Lissy93/dashy
          - title: Dashy Issues
            description: View open issues, or raise a new one
            icon: fas fa-bug
            url: https://github.com/Lissy93/dashy/issues    
kind: ConfigMap
metadata:
  labels:
    app: dashy
  name: dashy
  namespace: {{ dashy_namespace }}

roles/k3s_cluster/manifests/service.j2:

apiVersion: v1
kind: Service
metadata:
  name: dashy
  namespace: {{ dashy_namespace }}
spec:
  selector:
    app: dashy
  ports:
  - name: http
    port: 80

roles/k3s_cluster/manifests/ingress.j2:

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: dashy
  namespace: {{ dashy_namespace }}
spec:
  entryPoints:
    - web
    - websecure
  middlewares:
    - name: authme
      namespace: {{ dashy_namespace }}
  routes:
    - match: Host(`www.{{ letsencrypt_domain0 }}`)
      kind: Rule
      services:
        - name: dashy
          port: 80
      middlewares:
      - name: authme
        namespace: {{ dashy_namespace }}
    - match: Host(`{{ dashy_hostname }}`)
      kind: Rule
      services:
        - name: dashy
          port: 80
      middlewares:
      - name: authme
        namespace: {{ dashy_namespace }}
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: authme
  namespace: {{ dashy_namespace }}
spec:
  forwardAuth:
    address: http://{{ authelia_cluster_hostname }}/api/verify?rd=https://{{ authelia_hostname }}
    trustForwardHeader: true

The authme middleware is where the magic happens. It uses Traefik ForwardAuth to send the browser to Authelia to login and verify that the user is authorized to view the resource. The Authelia configuration determines which users or groups are allowed access using the access control rules.

Authforward diagram

Dashy Playbook

k3s-dashy.yml:

- hosts: master[0]
  become: yes
  vars:
    ansible_python_interpreter: /usr/bin/python3
  remote_user: ansible
  pre_tasks:
    - name: Install Kubernetes Python module
      pip:
        name: kubernetes
    - name: Install Kubernetes-validate Python module
      pip:
        name: kubernetes-validate
  roles:
    - role: k3s_cluster/dashy

Wrap up

Dashy is the perfect way to easily access all of my deployed applications as well any other frequently used sites on a very attractive and customizable page. It takes me back to when I used My Yahoo! as my homepage.

In future posts, I’ll continue sharing the Ansible playbooks and Kubernetes manifests I used to create my own application ecosystem.