NOTE / TERRAIN

Baby Buddy on Kubernetes: Keeping Family Data Close.

Baby Buddy is a small application with a very personal job: it keeps a family log close at hand without sending that history to somebody else’s platform.

From Kubernetes’ point of view the deployment is ordinary. One application pod talks to one PostgreSQL database. Traefik provides the web route. From my point of view the data is irreplaceable in a way that CPU time is not.

Browser
   │
   ▼
Traefik
   │
   ▼
Baby Buddy
   │
   ▼
PostgreSQL 16
   │
   ▼
5 GiB persistent volume

That is the whole architecture, and it is enough.

Why run it here?

Family tracking data is useful because it is detailed. The same detail makes it private. Self-hosting gives me control over where that history lives, when it is backed up and when it is removed.

The application does not need a large platform around it. It needs predictable availability, safe access and a database that can be restored.

The distinction I keep in mind is:

Baby Buddy container
  └── replaceable

PostgreSQL contents
  └── not replaceable

Kubernetes is good at recreating the first. My backup process has to protect the second.

Deployment layout

The repository uses a Kustomize base and two environment overlays:

babybuddy/
├── base/
│   ├── application Deployment
│   ├── PostgreSQL StatefulSet
│   ├── Services
│   └── namespace
├── overlays/
│   ├── homelab/
│   └── oci/
└── fleet.yaml

Rancher Fleet selects the appropriate overlay. Both environments use the babybuddy namespace, while each overlay owns its route and any cluster-specific changes.

The application and PostgreSQL each run as a single replica. This is not a highly available database design, and it does not pretend to be. For this workload, reliable recovery is more useful than adding distributed-database complexity.

Resources and storage

The application requests 100m CPU and 256 MiB memory, with limits of one CPU and 512 MiB memory. PostgreSQL uses the same request and limit shape.

The database has a 5 GiB persistent volume claim on local-path storage.

Those values tell me two useful things. First, the workload is inexpensive enough to remain resident. Second, the database is small enough that frequent logical backups are practical.

Small data deserves good backups too.

Configuration and secrets

The application needs a database connection and its own runtime settings. PostgreSQL needs its database name, user and password. These values are injected through Kubernetes configuration rather than written into this public description.

I treat the application secret and the database backup as a pair. A restored database without the expected connection material is not a restored service. Conversely, a perfect Secret does not help if the data volume is empty.

recovery set
  ├── PostgreSQL data
  ├── application and database secret inputs
  ├── Kustomize overlay
  └── ingress policy

Operating Baby Buddy

For an outage I start with both workloads:

kubectl -n babybuddy get deploy,statefulset,pod,svc,pvc,ingressroute
kubectl -n babybuddy logs deploy/babybuddy --tail=200
kubectl -n babybuddy logs statefulset/babybuddy-db --tail=200

Resource names can vary slightly after a manifest change, so I confirm them with kubectl get before using logs as a script.

The request path produces a useful decision tree:

route unavailable
  └── Traefik, Service or pod readiness

application starts but errors
  └── database connectivity or migrations

database pod Pending
  └── volume binding or node storage

page loads with an empty history
  └── stop and verify the mounted database before writing new data

The last situation is the one where patience matters. An empty interface can look healthy enough to invite new entries, which would make recovery and reconciliation more confusing.

Backup and recovery

For PostgreSQL I prefer a logical database backup plus an infrastructure-level volume snapshot. They answer different questions.

logical dump
  └── portable, inspectable database recovery

volume snapshot
  └── fast restoration of the exact filesystem state

The restore sequence is:

1. Restore the database secret inputs
2. Restore the PostgreSQL data or create a clean database
3. Reconcile the base and selected overlay
4. Import the logical backup if needed
5. Wait for application migrations
6. Sign in and check recent real records

The useful validation is not only a successful connection. I check that recent entries and expected history are present and that a new test entry can be written and read.

Upgrades

PostgreSQL is pinned to the 16 major line. The Baby Buddy application currently tracks a moving latest tag.

That makes the pre-upgrade database backup important. A Deployment rollback can restore an older container, but it cannot automatically reverse a schema migration already applied to PostgreSQL.

The safe order is:

backup
→ record running image
→ deploy
→ watch migrations and logs
→ validate the UI

Pinning the application to an explicit version would improve reproducibility and remains the cleaner long-term state.

Things worth remembering

At the time of writing:

Application     Baby Buddy, one Deployment
Database        PostgreSQL 16, one StatefulSet
Namespace       babybuddy
Storage         5 GiB local-path claim
Deployment      Kustomize + Rancher Fleet
Application tag latest
Recovery test   recent history plus a real write/read

This service is a good example of why resource size is a poor measure of importance. It consumes little. The data it carries deserves care.