How Database-Driven Kubernetes Automation Actually Works

Published: (December 3, 2025 at 07:13 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Mental Model

  1. LynqHub – Connects to your database and watches for changes.
  2. LynqForm – Defines the Kubernetes resources to create (templates).
  3. LynqNode – The actual instance, one per database row per template.

When a row appears in the database, the hub creates a LynqNode. The node controller renders the templates and applies the resources. When the row disappears or is deactivated, cleanup happens automatically.

LynqHub

The hub tells Lynq where your data lives and how to map columns to template variables.

apiVersion: operator.lynq.sh/v1
kind: LynqHub
metadata:
  name: my-saas-hub
spec:
  source:
    type: mysql
    mysql:
      host: mysql.default.svc.cluster.local
      port: 3306
      database: nodes
      table: node_data
      username: node_reader
      passwordRef:
        name: mysql-secret
        key: password
  syncInterval: 30s
  valueMappings:
    uid: node_id          # unique identifier for each node
    activate: is_active  # boolean controlling resource existence
  extraValueMappings:
    planId: subscription_plan
    region: deployment_region
  • valueMappings – Required columns (uid, activate).
  • extraValueMappings – Optional custom fields that become template variables.

The hub polls the database at syncInterval. If activate=true, Lynq creates resources; if it becomes false, cleanup starts.

LynqForm

A form is the blueprint that defines which resources to create for each database row.

apiVersion: operator.lynq.sh/v1
kind: LynqForm
metadata:
  name: web-app
spec:
  hubId: my-saas-hub
  deployments:
    - id: app
      nameTemplate: "{{ .uid }}-app"
      labelsTemplate:
        app: "{{ .uid }}"
        plan: "{{ .planId | default \"basic\" }}"
      spec:
        apiVersion: apps/v1
        kind: Deployment
        spec:
          replicas: 2
          template:
            spec:
              containers:
                - name: app
                  image: "{{ .deployImage | default \"nginx:latest\" }}"
  services:
    - id: svc
      nameTemplate: "{{ .uid }}-svc"
      dependIds: ["app"]
      spec:
        apiVersion: v1
        kind: Service
        # …

Templates use Go’s text/template syntax with Sprig functions, giving you over 200 helper functions out of the box. Common variables:

  • {{ .uid }} – unique name suffix.
  • {{ .planId | default "basic" }} – safe default for nullable columns.
  • {{ .uid | trunc63 }} – respects Kubernetes naming limits.

Resource Policies

Each resource can define its own creation, deletion, and conflict handling policies.

Creation Policy

deployments:
  - id: app
    creationPolicy: WhenNeeded   # default
    deletionPolicy: Delete
    conflictPolicy: Stuck
    patchStrategy: apply
  • WhenNeeded – Continuous sync; manual deletions are restored, template updates are applied.
  • Once – Create once and never touch again (ideal for init jobs).
jobs:
  - id: init-job
    creationPolicy: Once
    nameTemplate: "{{ .uid }}-init"
    spec:
      apiVersion: batch/v1
      kind: Job
      spec:
        template:
          spec:
            containers:
              - name: init
                command: ["sh", "-c", "echo 'one-time setup'"]
            restartPolicy: Never

Deletion Policy

  • Delete (default) – OwnerReference is set; Kubernetes garbage collection removes the resource.
  • Retain – Resource is kept; Lynq adds orphan labels for later discovery (useful for PVCs).
persistentVolumeClaims:
  - id: data-pvc
    deletionPolicy: Retain
    nameTemplate: "{{ .uid }}-data"

Conflict Policy

  • Stuck (default) – Reconciliation stops on conflict, emitting an event.
  • Force – Takes ownership via Server‑Side Apply with force=true (useful during migrations).

Dependency Management

Resources can depend on one another, ensuring correct creation order and optional readiness checks.

secrets:
  - id: db-creds
    nameTemplate: "{{ .uid }}-creds"

deployments:
  - id: db
    dependIds: ["db-creds"]
    waitForReady: true

  - id: app
    dependIds: ["db"]
    waitForReady: true

Lynq builds a directed acyclic graph (DAG) from dependIds and applies resources topologically. Cycles cause a fast failure.

  • waitForReady: true – Lynq waits for the dependency to become ready before proceeding.
  • skipOnDependencyFailure (default true) – Dependent resources are skipped if a dependency fails; set to false to force execution.
jobs:
  - id: cleanup-job
    dependIds: ["main-app"]
    skipOnDependencyFailure: false

Full Reconciliation Flow

  1. Hub controller polls the database every syncInterval.
  2. For each active row, it creates/updates a LynqNode CR.
  3. Node controller picks up the LynqNode.
  4. It renders all templates with the row’s data.
  5. Builds a dependency graph and sorts resources.
  6. Applies each resource in order using Server‑Side Apply.
  7. Waits for readiness if configured.
  8. Updates LynqNode status with the outcome.

Deactivation / Deletion

  1. Hub detects row deactivation or removal.
  2. Corresponding LynqNode CR is deleted.
  3. Finalizer runs cleanup based on each resource’s deletionPolicy.
    • Delete → resource removed.
    • Retain → orphan label added.

You can watch the process live:

kubectl get lynqnodes -w
kubectl describe lynqnode 

Sample status output:

status:
  desiredResources: 5
  readyResources: 5
  failedResources: 0
  appliedResources:
    - "Deployment/default/acme-app@app"
    - "Service/default/acme-svc@svc"

When to Use This Pattern

  • You already store business data in a relational database (users, tenants, orgs).
  • You need fast provisioning without the typical commit‑sync‑reconcile loops.
  • You must replicate the same set of resources many times with different values.
  • Template versioning is more important than per‑instance versioning.

It’s not ideal for a handful of unique environments where traditional IaC tools suffice. For large‑scale, data‑driven provisioning, Lynq offers a concise, declarative approach.

References & Hands‑On

  • Killercoda quick‑start (≈10 min)
  • Documentation
  • GitHub repository

Feel free to ask questions in the comments!

Back to Blog

Related posts

Read more »