Add Mealie recipe manager service

Deploy Mealie on minikube-indri for meal planning and prep automation.
Built from source via forge mirror (mirrors/mealie) with multi-stage
Dockerfile: Node.js frontend + Python/uv backend. Includes ArgoCD app,
k8s manifests, Caddy proxy entry, and service documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-03-16 21:07:25 -07:00
commit bd2b02f51c
12 changed files with 338 additions and 0 deletions

View file

@ -91,6 +91,9 @@ caddy_services:
- name: ollama
host: "ollama.{{ caddy_domain }}"
backend: "https://ollama.tail8d86e.ts.net"
- name: mealie
host: "meals.{{ caddy_domain }}"
backend: "https://meals.tail8d86e.ts.net"
- name: sifaka
host: "nas.{{ caddy_domain }}"
backend: "http://sifaka:5000"

17
argocd/apps/mealie.yaml Normal file
View file

@ -0,0 +1,17 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mealie
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@forge.ops.eblu.me:2222/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/mealie
destination:
server: https://kubernetes.default.svc
namespace: mealie
syncPolicy:
syncOptions:
- CreateNamespace=true

View file

@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: mealie
namespace: mealie
spec:
replicas: 1
selector:
matchLabels:
app: mealie
template:
metadata:
labels:
app: mealie
spec:
containers:
- name: mealie
image: registry.ops.eblu.me/blumeops/mealie:kustomized
ports:
- containerPort: 9000
env:
- name: BASE_URL
value: "https://meals.ops.eblu.me"
- name: ALLOW_SIGNUP
value: "false"
- name: TZ
value: "America/Los_Angeles"
- name: MAX_WORKERS
value: "1"
- name: WEB_CONCURRENCY
value: "1"
volumeMounts:
- name: data
mountPath: /app/data
resources:
requests:
memory: "128Mi"
cpu: "50m"
limits:
memory: "1000Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /api/app/about
port: 9000
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/app/about
port: 9000
initialDelaySeconds: 10
periodSeconds: 10
volumes:
- name: data
persistentVolumeClaim:
claimName: mealie-data

View file

@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mealie-tailscale
namespace: mealie
annotations:
tailscale.com/proxy-class: "default"
tailscale.com/proxy-group: "ingress"
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "Mealie"
gethomepage.dev/group: "Home"
gethomepage.dev/icon: "mealie.png"
gethomepage.dev/description: "Recipe manager"
gethomepage.dev/href: "https://meals.ops.eblu.me"
gethomepage.dev/pod-selector: "app=mealie"
spec:
ingressClassName: tailscale
defaultBackend:
service:
name: mealie
port:
number: 9000
tls:
- hosts:
- meals

View file

@ -0,0 +1,14 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mealie
resources:
- deployment.yaml
- service.yaml
- pvc.yaml
- ingress-tailscale.yaml
images:
- name: registry.ops.eblu.me/blumeops/mealie
newTag: v3.12.0-0000000

View file

@ -0,0 +1,13 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mealie-data
namespace: mealie
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 2Gi

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: mealie
namespace: mealie
spec:
selector:
app: mealie
ports:
- name: http
port: 9000
targetPort: 9000
protocol: TCP

View file

@ -0,0 +1,142 @@
# Mealie — self-hosted recipe manager
# Built from source via forge mirror of mealie-recipes/mealie
# Based on upstream docker/Dockerfile (multi-stage: Node frontend + Python backend)
ARG CONTAINER_APP_VERSION=v3.12.0
###############################################
# Frontend Build
###############################################
FROM node:24-slim AS frontend-builder
ARG CONTAINER_APP_VERSION
RUN apt-get update && apt-get install --no-install-recommends -y git ca-certificates && rm -rf /var/lib/apt/lists/*
RUN git clone --depth 1 --branch ${CONTAINER_APP_VERSION} \
https://forge.ops.eblu.me/mirrors/mealie.git /src
WORKDIR /src/frontend
RUN yarn install \
--prefer-offline \
--frozen-lockfile \
--non-interactive \
--production=false \
--network-timeout 1000000
RUN yarn generate
###############################################
# Python Base
###############################################
FROM python:3.12-slim AS python-base
ENV MEALIE_HOME="/app"
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100 \
VENV_PATH="/opt/mealie"
ENV PATH="$VENV_PATH/bin:$PATH"
RUN useradd -u 911 -U -d $MEALIE_HOME -s /bin/bash abc \
&& usermod -G users abc \
&& mkdir $MEALIE_HOME
###############################################
# Backend Package Build
###############################################
FROM python-base AS backend-builder
ARG CONTAINER_APP_VERSION
RUN apt-get update \
&& apt-get install --no-install-recommends -y curl git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN pip install uv
RUN git clone --depth 1 --branch ${CONTAINER_APP_VERSION} \
https://forge.ops.eblu.me/mirrors/mealie.git /src
WORKDIR /src
COPY --from=frontend-builder /src/frontend/dist ./mealie/frontend
RUN uv build --out-dir dist
RUN uv export --no-editable --no-emit-project --extra pgsql --format requirements-txt --output-file dist/requirements.txt \
&& MEALIE_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])") \
&& echo "mealie[pgsql]==${MEALIE_VERSION} \\" >> dist/requirements.txt \
&& pip hash dist/mealie-${MEALIE_VERSION}-py3-none-any.whl | tail -n1 | tr -d '\n' >> dist/requirements.txt \
&& echo " \\" >> dist/requirements.txt \
&& pip hash dist/mealie-${MEALIE_VERSION}.tar.gz | tail -n1 >> dist/requirements.txt
###############################################
# Python Venv Build
###############################################
FROM python-base AS venv-builder
RUN apt-get update \
&& apt-get install --no-install-recommends -y \
build-essential \
libpq-dev \
libwebp-dev \
ffmpeg \
libsasl2-dev libldap2-dev libssl-dev \
gnupg gnupg2 gnupg1 \
&& rm -rf /var/lib/apt/lists/*
RUN python3 -m venv --upgrade-deps $VENV_PATH
COPY --from=backend-builder /src/dist /dist
RUN . $VENV_PATH/bin/activate \
&& pip install --require-hashes -r /dist/requirements.txt --find-links /dist
###############################################
# Production Image
###############################################
FROM python-base AS production
ENV PRODUCTION=true
ENV TESTING=false
RUN apt-get update \
&& apt-get install --no-install-recommends -y \
curl \
ffmpeg \
gosu \
iproute2 \
libldap-common \
libldap2 \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /run/secrets
COPY --from=venv-builder $VENV_PATH $VENV_PATH
ENV NLTK_DATA="/nltk_data/"
RUN mkdir -p $NLTK_DATA
RUN python -m nltk.downloader -d $NLTK_DATA averaged_perceptron_tagger_eng
VOLUME ["$MEALIE_HOME/data/"]
ENV APP_PORT=9000
EXPOSE ${APP_PORT}
COPY --from=backend-builder /src/docker/healthcheck.sh $MEALIE_HOME/healthcheck.sh
RUN chmod +x $MEALIE_HOME/healthcheck.sh
HEALTHCHECK CMD $MEALIE_HOME/healthcheck.sh
ENV HOST=0.0.0.0
COPY --from=backend-builder /src/docker/entry.sh $MEALIE_HOME/run.sh
RUN chmod +x $MEALIE_HOME/run.sh
LABEL org.opencontainers.image.title="Mealie"
LABEL org.opencontainers.image.description="Self-hosted recipe manager"
LABEL org.opencontainers.image.source="https://github.com/mealie-recipes/mealie"
ENTRYPOINT ["/app/run.sh"]

View file

@ -0,0 +1 @@
Deploy Mealie recipe manager on minikube-indri for meal planning and prep automation.

View file

@ -40,6 +40,7 @@ DNS points to [[indri]]'s Tailscale IP. TLS via Let's Encrypt (ACME DNS-01 with
| [[navidrome]] | https://dj.ops.eblu.me | Music streaming |
| [[jellyfin]] | https://jellyfin.ops.eblu.me | Media server |
| [[postgresql]] | pg.ops.eblu.me:5432 | Database |
| [[mealie]] | https://meals.ops.eblu.me | Recipe manager |
| [[sifaka|Sifaka]] | https://nas.ops.eblu.me | NAS dashboard |
## Public Services (`*.eblu.me`)

View file

@ -39,6 +39,7 @@ Registry of all applications deployed via [[argocd]].
| `cv` | cv | `argocd/manifests/cv/` | [[cv]] |
| `forgejo-runner` | forgejo-runner | `argocd/manifests/forgejo-runner/` | [[forgejo]] CI |
| `ollama` | ollama | `argocd/manifests/ollama/` | [[ollama]] |
| `mealie` | mealie | `argocd/manifests/mealie/` | [[mealie]] |
## Sync Policies

View file

@ -0,0 +1,51 @@
---
title: Mealie
modified: 2026-03-16
tags:
- service
- recipes
---
# Mealie
Self-hosted recipe manager with a REST API. Part of the meal planning pipeline: Mealie stores categorized recipes, a planner script selects balanced meals, and [[ollama]] generates a unified cooking timeline.
## Quick Reference
| Property | Value |
|----------|-------|
| **URL** | https://meals.ops.eblu.me |
| **Tailscale URL** | https://meals.tail8d86e.ts.net |
| **Namespace** | `mealie` |
| **Image** | `registry.ops.eblu.me/blumeops/mealie` (built from source) |
| **Database** | SQLite (local, at `/app/data/`) |
| **API Docs** | https://meals.ops.eblu.me/docs |
| **Upstream** | https://github.com/mealie-recipes/mealie |
| **Manifests** | `argocd/manifests/mealie/` |
## Features
- Full REST API (FastAPI) for recipe CRUD, filtering by tag/category
- Structured recipe data: ingredients (quantity/unit/food), step-by-step instructions
- Built-in meal planning and shopping lists
- Recipe import from URLs
- API token auth for automation
## Storage
- 2Gi PVC at `/app/data/` via `local-path` storageClassName
- SQLite database (sufficient for single-user, no network storage concerns on minikube)
- Recipe images and assets stored alongside the database
## Networking
| Endpoint | Reachable from |
|----------|----------------|
| `https://meals.ops.eblu.me` | Tailnet clients (via Caddy) |
| `https://meals.tail8d86e.ts.net` | Tailnet clients |
| `http://mealie.mealie.svc.cluster.local:9000` | In-cluster |
## Related
- [[ollama]] — LLM backend for meal timeline generation
- [[borgmatic]] — Data backup