A Small Hoisted Mealie Deployment on k3s

We will be deploying Mealie (self-hosted recipe manager) on a k3s cluster using a Deployment and LoadBalancer Service.


How We Found the Image

Mealie does not publish a dedicated Kubernetes manifest or Helm chart. The process was:

  1. Found the Docker image reference first — docker pull ghcr.io/mealie-recipes/mealie
  2. Went to the GitHub Container Registry page to find available tags
  3. Picked a stable release tag (v3.13.0) rather than latest or nightly — pinning a version is best practice so upgrades are deliberate and controlled
  4. Used that image reference directly in the Kubernetes Deployment manifest

This is a common pattern — many self-hosted apps only publish Docker images. We find the image, check the registry for stable tags, and build oir own k8s manifests around it.


Container Image


Step 1 — Create the Namespace

kubectl create namespace mealie

Set the namespace as default context so we don’t have to type -n mealie every time:

k config set-context default --namespace=mealie
k config get-contexts

Expected output:

CURRENT   NAME      CLUSTER   AUTHINFO   NAMESPACE
*         default   default   default    mealie

Step 2 — Create the Deployment

Create mealie.yaml:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: mealie
  name: mealie
  namespace: mealie
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mealie
  template:
    metadata:
      labels:
        app: mealie
    spec:
      containers:
      - image: ghcr.io/mealie-recipes/mealie:v3.13.0
        name: mealie
        ports:
          - containerPort: 9000

Apply it:

k apply -f mealie.yaml
k get pods -o wide

Expected output:

NAME                      READY   STATUS    RESTARTS   AGE   IP            NODE
mealie-6f6cfcb6fc-g2zfn   1/1     Running   0          17s   10.42.0.151   k3s-cp-01

k get deployments.apps

NAME READY UP-TO-DATE AVAILABLE AGE
mealie 1/1 1 1 28s

Step 3 — Test with Port Forward

Before creating a proper service, test the pod is working with port-forward:

k port-forward services/mealie 9000:9000 --address 0.0.0.0

--address 0.0.0.0 is required to bind on all interfaces — without it, only 127.0.0.1 (the node itself) is accessible, not external IPs.

Test from your machine:

curl -v http://172.16.77.10:9000/

  • Trying 172.16.77.10:9000…
  • Established connection to 172.16.77.10 (172.16.77.10 port 9000) from 192.168.0.47 port 64018
  • using HTTP/1.x

    GET / HTTP/1.1
    Host: 172.16.77.10:9000
    User-Agent: curl/8.18.0


Step 4 — Expose as LoadBalancer Service

First expose it quickly with kubectl expose, then export to yaml to clean it up:

k expose deployment mealie --port 9000
k get services mealie -o yaml > services.yml

Edit services.yml — strip all the auto-generated noise and set type to LoadBalancer:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: mealie
  name: mealie
  namespace: mealie
spec:
  ports:
  - port: 9000
    protocol: TCP
    targetPort: 9000
  selector:
    app: mealie
  type: LoadBalancer

Delete the old ClusterIP service and apply the clean one:

k delete svc mealie
k apply -f services.yml
k get svc

Expected output:

NAME     TYPE           CLUSTER-IP     EXTERNAL-IP                              PORT(S)          AGE
mealie   LoadBalancer   10.43.12.132   172.16.77.10,172.16.77.11,172.16.77.12   9000:31952/TCP   21s

How the LoadBalancer Works in k3s

k3s uses Klipper as its built-in load balancer. It works by assigning all our node IPs as external IPs, so traffic to any of our nodes on that port gets routed to the service.

That’s why we see all 3 IPs:

  • 172.16.77.10 — k3s-cp-01
  • 172.16.77.11 — worker-01
  • 172.16.77.12 — worker-02

Klipper exposes the service on all node IPs. So if any one node goes down, we can still reach mealie via the other two IPs.


Final Result

Mealie is accessible at:

  • http://172.16.77.10:9000
  • http://172.16.77.11:9000
  • http://172.16.77.12:9000

Commit to Git

git add mealie.yaml services.yml
git commit -m "Add mealie deployment and LoadBalancer service"
git push

(Visited 1 times, 1 visits today)

By Ash Thomas

Ash Thomas is a seasoned IT professional with extensive experience as a technical expert, complemented by a keen interest in blockchain technology.