blumeops/docs/tutorials/adding-a-service.md
Erich Blume b0bac91ca9 Fix frontmatter field name for Quartz date display (#158)
## Summary

- Rename `date-modified` -> `modified` in all 80 docs and the `docs-check-frontmatter` task

Quartz's `CreatedModifiedDate` plugin recognizes `modified`, `lastmod`, `updated`, and `last-modified` — but not `date-modified`. The wrong field name caused Quartz to ignore frontmatter dates entirely and fall through to filesystem timestamps (UTC inside Dagger), showing Feb 12 on pages built late on Feb 11 PST.

## Test plan

- [x] `mise run docs-check-frontmatter` passes
- [ ] Kick off docs release after merge — verify rendered dates match frontmatter values

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/158
2026-02-11 16:45:12 -08:00

5.6 KiB

title modified tags
Adding a Service 2026-02-07
tutorials
argocd
kubernetes

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
  • kubectl configured with minikube-indri context
  • argocd CLI installed (via Brewfile: brew bundle)

Overview

Adding a service involves:

  1. Creating Kubernetes manifests
  2. Creating an ArgoCD Application
  3. Configuring Tailscale ingress
  4. Adding Homepage dashboard entry
  5. 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