Compare commits

...
Sign in to create a new pull request.

7 commits

Author SHA1 Message Date
b0023fef92 Switch Mealie OIDC to confidential client
Mealie requires OIDC_CLIENT_SECRET even though its docs say "public
client with PKCE". The token exchange happens server-side in Mealie's
Python backend, so the secret never reaches the browser.

- Generate client secret, store in 1Password
- Add to Authentik external-secret and worker env
- Switch blueprint from public to confidential
- Add ExternalSecret for mealie namespace
- Update docs to reflect confidential client

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 21:50:34 -07:00
7dce0abbb9 Update docs: fix mealie storageClass, borgmatic SQLite backups, federated-login
- mealie.md: fix storageClassName to standard, add auth/backup sections
- borgmatic.md: document k8s SQLite dump pattern and mealie entry
- federated-login.md: remove ArgoCD from future work (already done),
  add mealie to related links

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 21:38:36 -07:00
c411862fda Add Mealie to service-versions.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 21:36:55 -07:00
ac83bd14e3 Add borgmatic backup for Mealie SQLite, set image tag
- Add before_backup hook to borgmatic: kubectl exec + python3 sqlite3
  .backup to safely dump the database, then kubectl cp to host
- Include k8s-dumps directory in borgmatic source_directories
- Generic pattern: borgmatic_k8s_sqlite_dumps list in defaults
- Fix PVC storageClassName: standard (not local-path) on minikube
- Set container image tag to v3.12.0-5c5fd18 from CI build

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 21:36:00 -07:00
30a114462c Allow all Authentik users to access Mealie
Remove admins-only policy binding from Mealie app. Any authenticated
Authentik user can log in (account auto-created). Mealie's
OIDC_ADMIN_GROUP=admins handles admin privilege mapping internally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 21:19:26 -07:00
5c5fd18cac Add Authentik OIDC integration for Mealie
Configure Mealie as a public PKCE client in Authentik. Mealie's OIDC
flow runs client-side (Vue.js SPA) so it uses PKCE instead of a
client_secret. No 1Password secret or ExternalSecret needed.

- Add mealie.yaml blueprint to Authentik configmap (public client, admins group)
- Add OIDC env vars to Mealie deployment
- Update service docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 21:15:21 -07:00
bd2b02f51c 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>
2026-03-16 21:07:25 -07:00
22 changed files with 489 additions and 3 deletions

View file

@ -16,6 +16,7 @@ borgmatic_source_directories:
- /opt/homebrew/var/forgejo - /opt/homebrew/var/forgejo
- /Users/erichblume/.config/borgmatic - /Users/erichblume/.config/borgmatic
- /Users/erichblume/Documents - /Users/erichblume/Documents
- /Users/erichblume/.local/share/borgmatic/k8s-dumps
# Backup repositories # Backup repositories
borgmatic_repositories: borgmatic_repositories:
@ -31,6 +32,19 @@ borgmatic_repositories:
# BorgBase SSH key (fetched from 1Password in playbook pre_tasks) # BorgBase SSH key (fetched from 1Password in playbook pre_tasks)
borgmatic_borgbase_ssh_key_path: /Users/erichblume/.ssh/borgbase_ed25519 borgmatic_borgbase_ssh_key_path: /Users/erichblume/.ssh/borgbase_ed25519
# Directory for pre-backup database dumps from k8s pods
borgmatic_k8s_dump_dir: /Users/erichblume/.local/share/borgmatic/k8s-dumps
# K8s SQLite databases to dump before backup via kubectl exec
# Each entry runs: kubectl exec <pod-selector> -- sqlite3 <path> ".backup /tmp/backup.db"
# then copies the dump to borgmatic_k8s_dump_dir/<name>.db
borgmatic_k8s_sqlite_dumps:
- name: mealie
namespace: mealie
label_selector: app=mealie
db_path: /app/data/mealie.db
context: minikube-indri
# Exclude patterns # Exclude patterns
borgmatic_exclude_patterns: [] borgmatic_exclude_patterns: []

View file

@ -33,6 +33,13 @@
key: "u3ugi1x1.repo.borgbase.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGU0mISTyHBw9tBs6SuhSq8tvNM8m9eifQxM+88TowPO" key: "u3ugi1x1.repo.borgbase.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGU0mISTyHBw9tBs6SuhSq8tvNM8m9eifQxM+88TowPO"
state: present state: present
- name: Ensure k8s dump directory exists
ansible.builtin.file:
path: "{{ borgmatic_k8s_dump_dir }}"
state: directory
mode: '0700'
when: borgmatic_k8s_sqlite_dumps | length > 0
- name: Deploy borgmatic configuration - name: Deploy borgmatic configuration
ansible.builtin.template: ansible.builtin.template:
src: config.yaml.j2 src: config.yaml.j2

View file

@ -31,6 +31,16 @@ exclude_patterns:
encryption_passcommand: {{ borgmatic_encryption_passcommand }} encryption_passcommand: {{ borgmatic_encryption_passcommand }}
{% if borgmatic_k8s_sqlite_dumps %}
# Pre-backup: dump SQLite databases from k8s pods
# Uses sqlite3 .backup for a safe, consistent copy (no corruption from concurrent writes)
before_backup:
- mkdir -p {{ borgmatic_k8s_dump_dir }}
{% for db in borgmatic_k8s_sqlite_dumps %}
- /opt/homebrew/bin/kubectl --context={{ db.context }} exec -n {{ db.namespace }} deploy/{{ db.name }} -- python3 -c "import sqlite3; sqlite3.connect('{{ db.db_path }}').backup(sqlite3.connect('/tmp/{{ db.name }}-backup.db'))" && /opt/homebrew/bin/kubectl --context={{ db.context }} cp {{ db.namespace }}/$(/opt/homebrew/bin/kubectl --context={{ db.context }} get pod -n {{ db.namespace }} -l {{ db.label_selector }} -o jsonpath='{.items[0].metadata.name}'):/tmp/{{ db.name }}-backup.db {{ borgmatic_k8s_dump_dir }}/{{ db.name }}.db
{% endfor %}
{% endif %}
ssh_command: ssh -o IdentitiesOnly=yes -i {{ borgmatic_borgbase_ssh_key_path }} ssh_command: ssh -o IdentitiesOnly=yes -i {{ borgmatic_borgbase_ssh_key_path }}
# Retention policy # Retention policy

View file

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

@ -345,3 +345,47 @@ data:
provider: !KeyOf jellyfin-provider provider: !KeyOf jellyfin-provider
meta_launch_url: https://jellyfin.ops.eblu.me meta_launch_url: https://jellyfin.ops.eblu.me
policy_engine_mode: all policy_engine_mode: all
mealie.yaml: |
version: 1
metadata:
name: BlumeOps Mealie SSO
labels:
blueprints.goauthentik.io/description: "Mealie OIDC provider and application"
entries:
# OAuth2 provider for Mealie (confidential — Mealie requires client_secret)
- model: authentik_providers_oauth2.oauth2provider
id: mealie-provider
identifiers:
name: Mealie
attrs:
name: Mealie
authorization_flow: !Find [authentik_flows.flow, [slug, default-provider-authorization-implicit-consent]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
client_type: confidential
client_id: mealie
client_secret: !Env AUTHENTIK_MEALIE_CLIENT_SECRET
redirect_uris:
- matching_mode: strict
url: https://meals.ops.eblu.me/login
- matching_mode: strict
url: https://meals.tail8d86e.ts.net/login
signing_key: !Find [authentik_crypto.certificatekeypair, [name, authentik Self-signed Certificate]]
property_mappings:
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, openid]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, email]]
- !Find [authentik_providers_oauth2.scopemapping, [scope_name, profile]]
sub_mode: hashed_user_id
include_claims_in_id_token: true
# Mealie application — all authenticated users allowed (admin mapped via OIDC_ADMIN_GROUP)
- model: authentik_core.application
id: mealie-app
identifiers:
slug: mealie
attrs:
name: Mealie
slug: mealie
provider: !KeyOf mealie-provider
meta_launch_url: https://meals.ops.eblu.me
policy_engine_mode: all

View file

@ -78,6 +78,11 @@ spec:
secretKeyRef: secretKeyRef:
name: authentik-config name: authentik-config
key: argocd-client-secret key: argocd-client-secret
- name: AUTHENTIK_MEALIE_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: authentik-config
key: mealie-client-secret
volumeMounts: volumeMounts:
- name: blueprints - name: blueprints
mountPath: /blueprints/custom mountPath: /blueprints/custom

View file

@ -57,3 +57,7 @@ spec:
remoteRef: remoteRef:
key: "Authentik (blumeops)" key: "Authentik (blumeops)"
property: argocd-client-secret property: argocd-client-secret
- secretKey: mealie-client-secret
remoteRef:
key: "Authentik (blumeops)"
property: mealie-client-secret

View file

@ -0,0 +1,79 @@
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"
# OIDC — Authentik (public client, PKCE)
- name: OIDC_AUTH_ENABLED
value: "true"
- name: OIDC_CONFIGURATION_URL
value: "https://authentik.ops.eblu.me/application/o/mealie/.well-known/openid-configuration"
- name: OIDC_CLIENT_ID
value: "mealie"
- name: OIDC_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: mealie-oidc
key: client-secret
- name: OIDC_AUTO_REDIRECT
value: "false"
- name: OIDC_PROVIDER_NAME
value: "Authentik"
- name: OIDC_ADMIN_GROUP
value: "admins"
- name: OIDC_SIGNUP_ENABLED
value: "true"
- name: OIDC_USER_CLAIM
value: "email"
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,19 @@
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mealie-oidc
namespace: mealie
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: mealie-oidc
creationPolicy: Owner
data:
- secretKey: client-secret
remoteRef:
key: "Authentik (blumeops)"
property: mealie-client-secret

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,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mealie
resources:
- deployment.yaml
- service.yaml
- pvc.yaml
- ingress-tailscale.yaml
- external-secret.yaml
images:
- name: registry.ops.eblu.me/blumeops/mealie
newTag: v3.12.0-5c5fd18

View file

@ -0,0 +1,13 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mealie-data
namespace: mealie
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
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

@ -76,11 +76,12 @@ Authentik enforces TOTP MFA on its default authentication flow (`not_configured_
## Future Work ## Future Work
- **Additional services:** ArgoCD, Miniflux, Immich - **Additional services:** Miniflux, Immich
## Related ## Related
- [[authentik]] - OIDC identity provider reference - [[authentik]] - OIDC identity provider reference
- [[grafana]] - First OIDC client - [[grafana]] - First OIDC client
- [[mealie]] - Recipe manager (public PKCE client)
- [[security-model]] - Network security and access control - [[security-model]] - Network security and access control
- [[deploy-authentik]] - Deployment how-to - [[deploy-authentik]] - Deployment how-to

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 | | [[navidrome]] | https://dj.ops.eblu.me | Music streaming |
| [[jellyfin]] | https://jellyfin.ops.eblu.me | Media server | | [[jellyfin]] | https://jellyfin.ops.eblu.me | Media server |
| [[postgresql]] | pg.ops.eblu.me:5432 | Database | | [[postgresql]] | pg.ops.eblu.me:5432 | Database |
| [[mealie]] | https://meals.ops.eblu.me | Recipe manager |
| [[sifaka|Sifaka]] | https://nas.ops.eblu.me | NAS dashboard | | [[sifaka|Sifaka]] | https://nas.ops.eblu.me | NAS dashboard |
## Public Services (`*.eblu.me`) ## Public Services (`*.eblu.me`)

View file

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

View file

@ -1,6 +1,6 @@
--- ---
title: Borgmatic title: Borgmatic
modified: 2026-02-10 modified: 2026-03-16
tags: tags:
- service - service
- backup - backup
@ -26,11 +26,15 @@ Daily backup system using Borg backup, running on indri.
- `/opt/homebrew/var/forgejo` - Git forge data - `/opt/homebrew/var/forgejo` - Git forge data
- `~/.config/borgmatic` - Borgmatic config - `~/.config/borgmatic` - Borgmatic config
- `~/Documents` - Personal documents - `~/Documents` - Personal documents
- `~/.local/share/borgmatic/k8s-dumps/` - SQLite dumps from k8s pods
**Databases:** **PostgreSQL databases:**
- `miniflux` on [[postgresql]] - `miniflux` on [[postgresql]]
- `teslamate` on [[postgresql]] - `teslamate` on [[postgresql]]
**K8s SQLite databases (pre-backup dump via kubectl exec):**
- [[mealie]] - Recipe manager (`/app/data/mealie.db`)
**Not backed up (by design):** **Not backed up (by design):**
- ZIM archives (re-downloadable) - ZIM archives (re-downloadable)
- Prometheus metrics (ephemeral) - Prometheus metrics (ephemeral)

View file

@ -0,0 +1,61 @@
---
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
- OIDC login via [[authentik]] (confidential client)
## Authentication
OIDC via [[authentik]] using a confidential client. Client secret stored in 1Password (`Authentik (blumeops)` / `mealie-client-secret`) and delivered via ExternalSecret. All Authentik users can log in; members of the `admins` group get Mealie admin privileges via `OIDC_ADMIN_GROUP`.
## Storage
- 2Gi PVC at `/app/data/` via `standard` storageClassName (minikube-hostpath)
- SQLite database (sufficient for single-user)
- Recipe images and assets stored alongside the database
## Backup
SQLite database backed up via [[borgmatic]]'s `before_backup` hook. Borgmatic runs `kubectl exec` to create a safe `.backup` copy (via Python's `sqlite3` module), then `kubectl cp` to the host. The dump lands in `~/.local/share/borgmatic/k8s-dumps/mealie.db` and is included in both local (sifaka) and offsite (BorgBase) backups.
## 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
- [[authentik]] — OIDC identity provider
- [[ollama]] — LLM backend for meal timeline generation
- [[borgmatic]] — Data backup

View file

@ -253,6 +253,13 @@ services:
upstream-source: https://code.forgejo.org/forgejo/runner/releases upstream-source: https://code.forgejo.org/forgejo/runner/releases
notes: Forgejo runner on ringtail via nixpkgs; version tracks flake.lock notes: Forgejo runner on ringtail via nixpkgs; version tracks flake.lock
- name: mealie
type: argocd
last-reviewed: 2026-03-16
current-version: "v3.12.0"
upstream-source: https://github.com/mealie-recipes/mealie/releases
notes: Recipe manager; built from source via forge mirror
- name: unpoller - name: unpoller
type: argocd type: argocd
last-reviewed: 2026-03-16 last-reviewed: 2026-03-16