## Summary Migrates the docs build pipeline to Dagger (Phase 2 of the Dagger CI adoption plan). - **Backfill `date-modified` frontmatter** on all 80 docs — Dagger's `--src=.` excludes `.git`, so Quartz can't use git history for page dates. Frontmatter dates work with or without git. - **New `docs-check-frontmatter` mise task + pre-commit hook** — validates all docs have `title`, `tags`, and `date-modified` - **New Dagger functions** — `build_changelog` (towncrier in Python container) and `build_docs` (chains changelog → Quartz build in Node container, returns tarball) - **Simplified CI workflow** — the ~44-line inline Quartz build (clone, npm ci, build, tar, cleanup) is replaced by `dagger call build-docs`. Changelog step remains local on the runner since towncrier needs to modify the host working tree for the git commit. ### Design decisions - **Towncrier runs twice in CI**: once inside Dagger (for the docs tarball) and once on the runner (for the git commit). This is intentional — Dagger's directory export is additive and can't delete the consumed changelog fragments from the host. - **Artifact hosting stays on Forgejo Releases** (not migrated to Forgejo Packages as the plan doc originally suggested). That migration can happen independently. - **`date-modified` frontmatter** preserved even though `build_changelog` installs git — the git there is only for towncrier's `git add` call, not for history. The local iteration story (`dagger call build-docs --src=. --version=dev` with uncommitted changes) depends on frontmatter dates. ### Local iteration ```bash dagger call build-docs --src=. --version=dev export --path=./docs-dev.tar.gz tar tf docs-dev.tar.gz | head -20 ``` ## Deployment and Testing - [x] `dagger call build-docs --src=. --version=dev` produces valid 1.1MB tarball (149 HTML pages) - [x] Pre-commit hooks pass (including new `docs-check-frontmatter`) - [ ] Full `workflow_dispatch` run after merge 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/157
5.6 KiB
| title | date-modified | tags | |||
|---|---|---|---|---|---|
| Adding a Service | 2026-02-07 |
|
Adding an ArgoCD-Managed Service
Audiences: Contributor, Replicator
This tutorial walks through deploying a new service to BlumeOps via ArgoCD, including ingress configuration, homepage integration, and observability setup.
Prerequisites
- Access to the tailscale network
kubectlconfigured withminikube-indricontextargocdCLI installed (via Brewfile:brew bundle)
Overview
Adding a service involves:
- Creating Kubernetes manifests
- Creating an ArgoCD Application
- Configuring Tailscale ingress
- Adding Homepage dashboard entry
- Setting up Grafana dashboards (optional)
Step 1: Create Manifests Directory
Create a directory for your service's Kubernetes manifests:
argocd/manifests/<service-name>/
├── deployment.yaml
├── service.yaml
├── ingress-tailscale.yaml
└── configmap.yaml # if needed
Example Deployment
# argocd/manifests/myservice/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myservice
namespace: myservice
spec:
replicas: 1
selector:
matchLabels:
app: myservice
template:
metadata:
labels:
app: myservice
spec:
containers:
- name: myservice
image: registry.ops.eblu.me/myservice:v1.0.0
ports:
- containerPort: 8080
Example Service
# argocd/manifests/myservice/service.yaml
apiVersion: v1
kind: Service
metadata:
name: myservice
namespace: myservice
spec:
selector:
app: myservice
ports:
- port: 80
targetPort: 8080
Step 2: Configure Tailscale Ingress
Create an Ingress to expose the service via Tailscale. See tailscale-operator for details.
# argocd/manifests/myservice/ingress-tailscale.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myservice
namespace: myservice
spec:
ingressClassName: tailscale
rules:
- host: myservice
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myservice
port:
number: 80
This exposes the service at https://myservice.tail8d86e.ts.net.
Step 3: Add Homepage Annotations
Add annotations to the Ingress for automatic Homepage dashboard discovery:
metadata:
annotations:
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "My Service"
gethomepage.dev/group: "Apps"
gethomepage.dev/icon: "myservice.png"
gethomepage.dev/description: "Short description"
gethomepage.dev/href: "https://myservice.ops.eblu.me"
gethomepage.dev/pod-selector: "app=myservice"
Icons use Dashboard Icons format.
Step 4: Create ArgoCD Application
Create an Application manifest to tell ArgoCD about your service:
# argocd/apps/myservice.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myservice
namespace: argocd
spec:
project: default
source:
repoURL: ssh://forgejo@indri.tail8d86e.ts.net:2200/eblume/blumeops.git
targetRevision: main
path: argocd/manifests/myservice
destination:
server: https://kubernetes.default.svc
namespace: myservice
syncPolicy:
syncOptions:
- CreateNamespace=true
Step 5: Add Caddy Route (Optional)
If the service needs to be accessible from other pods or containers, add a Caddy route in ansible/roles/caddy/defaults/main.yml:
caddy_services:
# ... existing services ...
- name: myservice
upstream: "https://myservice.tail8d86e.ts.net"
Then run mise run provision-indri -- --tags caddy to apply.
This enables access via https://myservice.ops.eblu.me. See routing for details on when this is needed.
Step 6: Deploy
Testing on a Feature Branch
For new services, point ArgoCD at your feature branch first:
# Sync the apps application to pick up your new Application
argocd app sync apps
# Point your app at the feature branch
argocd app set myservice --revision feature/your-branch
argocd app sync myservice
Verify Deployment
kubectl --context=minikube-indri -n myservice get pods
kubectl --context=minikube-indri -n myservice logs -f deployment/myservice
After PR Merge
Reset to main branch:
argocd app set myservice --revision main
argocd app sync myservice
Step 7: Add Observability (Optional)
Prometheus Metrics
If your service exposes Prometheus metrics, add scrape annotations:
# In deployment.yaml pod template
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/metrics"
Grafana Dashboard
Create a ConfigMap in argocd/manifests/grafana-config/dashboards/:
apiVersion: v1
kind: ConfigMap
metadata:
name: myservice-dashboard
namespace: monitoring
labels:
grafana_dashboard: "1"
annotations:
grafana_folder: "Services"
data:
myservice.json: |
{ ... dashboard JSON ... }
See grafana for dashboard provisioning details.
Checklist
- Manifests created in
argocd/manifests/<service>/ - ArgoCD Application created in
argocd/apps/ - Tailscale Ingress configured
- Homepage annotations added
- Caddy route added (if needed for pod access)
- Feature branch tested via ArgoCD
- Metrics/dashboard configured (if applicable)
- PR created and reviewed
- Reset to main after merge
Related
- argocd - GitOps platform
- tailscale-operator - Kubernetes ingress
- routing - Service routing options
- grafana - Dashboard configuration
- apps - Application registry