Integrations · Logs · Grafana Alloy

Grafana Alloy to Nightlamp

Alloy is Promtail's successor in the Grafana stack — same Loki push protocol, very different config syntax. This guide wires Alloy's loki.source.kubernetes component through loki.write to Nightlamp, including a Promtail → Alloy migration note for teams switching over.

Why Alloy

Grafana deprecated Promtail in 2024 in favor of Alloy. New clusters should reach for Alloy first; existing Promtail deployments work indefinitely but won't get new features. Alloy's config language is River — a typed HCL-like syntax that catches misconfigurations at startup instead of at first push.

Coming from Promtail? Promtail's clients[] becomes Alloy's loki.write; scrape_configs[] becomes loki.source.kubernetes plus discovery.kubernetes components. Relabeling moves from inline relabel_configs blocks to a separate discovery.relabel component. See the migration table at the bottom of this page.

Prerequisites

  • A Kubernetes cluster you can apply manifests to.
  • A Nightlamp app registered — app ID + DSN key.
  • Alloy v1.4+ (Oct 2024). Earlier preview versions used an older River dialect.

1. ServiceAccount + RBAC

rbac.yaml

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: alloy
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: alloy-read
rules:
  - apiGroups: [""]
    resources: ["namespaces", "nodes", "pods"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: alloy-read
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: alloy-read
subjects:
  - kind: ServiceAccount
    name: alloy
    namespace: default

2. Secret with your DSN

Create the Secret

kubectl create secret generic alloy-dsn \
  --namespace=default \
  --from-literal=dsn=<your-dsn-key>

3. ConfigMap (River)

The pipeline as four River components: discovery.kubernetes finds pods, discovery.relabel keeps only opted-in workloads and shapes the label set, loki.source.kubernetes tails the discovered log files, and loki.write ships them to Nightlamp with the auth headers.

configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: alloy-config
  namespace: default
data:
  config.alloy: |
    // — Discovery —————————————————————————————————————
    discovery.kubernetes "pods" {
      role = "pod"
    }

    // Keep only pods carrying the nightlamp.app/id label, and reshape
    // the labels that ride along on each line.
    discovery.relabel "pods" {
      targets = discovery.kubernetes.pods.targets

      rule {
        source_labels = ["__meta_kubernetes_pod_label_nightlamp_app_id"]
        action        = "keep"
        regex         = ".+"
      }
      rule {
        source_labels = ["__meta_kubernetes_pod_label_nightlamp_app_id"]
        target_label  = "app"
      }
      rule {
        source_labels = ["__meta_kubernetes_pod_name"]
        target_label  = "pod"
      }
      rule {
        source_labels = ["__meta_kubernetes_namespace"]
        target_label  = "namespace"
      }
      rule {
        source_labels = ["__meta_kubernetes_pod_container_name"]
        target_label  = "container"
      }
    }

    // — Source ————————————————————————————————————————
    loki.source.kubernetes "pods" {
      targets    = discovery.relabel.pods.output
      forward_to = [loki.process.drop_health.receiver]
    }

    // Drop 2xx /health* probe spam.
    loki.process "drop_health" {
      stage.drop {
        expression = "\"/health[^\"]*\" 2\\d\\d"
      }
      forward_to = [loki.write.nightlamp.receiver]
    }

    // — Sink ——————————————————————————————————————————
    loki.write "nightlamp" {
      endpoint {
        url = "https://api.nightlamp.app/api/loki/api/v1/push"
        headers = {
          "X-Nightlamp-App-Id"  = "<your-app-id>",
          "X-Nightlamp-Dsn-Key" = env("NIGHTLAMP_DSN"),
        }
      }
    }

4. DaemonSet

Alloy's official image expects the config at /etc/alloy/config.alloy and exposes port 12345 for the local debugging UI. The DSN env var sources from the Secret above.

daemonset.yaml

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: alloy
  namespace: default
spec:
  selector:
    matchLabels: { app: alloy }
  template:
    metadata:
      labels: { app: alloy }
    spec:
      serviceAccountName: alloy
      tolerations:
        - operator: Exists
          effect: NoSchedule
        - operator: Exists
          effect: NoExecute
      containers:
        - name: alloy
          image: grafana/alloy:v1.4.2
          args:
            - run
            - /etc/alloy/config.alloy
            - --server.http.listen-addr=0.0.0.0:12345
            - --storage.path=/var/lib/alloy/data
          env:
            - name: NIGHTLAMP_DSN
              valueFrom:
                secretKeyRef:
                  name: alloy-dsn
                  key: dsn
          ports:
            - { name: http, containerPort: 12345 }
          resources:
            requests: { cpu: 100m, memory: 128Mi }
            limits:   { cpu: 500m, memory: 512Mi }
          volumeMounts:
            - { name: config,  mountPath: /etc/alloy }
            - { name: varlog,  mountPath: /var/log, readOnly: true }
            - { name: data,    mountPath: /var/lib/alloy/data }
      volumes:
        - name: config
          configMap: { name: alloy-config }
        - { name: varlog, hostPath: { path: /var/log } }
        - { name: data,   hostPath: { path: /var/lib/alloy/data, type: DirectoryOrCreate } }

5. Opt your workloads in

In each app's Deployment manifest

spec:
  template:
    metadata:
      labels:
        nightlamp.app/id: <your-app-id>

6. Apply + verify

  1. Apply the manifests

    rbac → secret → configmap → daemonset

    kubectl apply -f rbac.yaml
    kubectl apply -f configmap.yaml
    kubectl apply -f daemonset.yaml
  2. Open Alloy's debug UI

    Port-forward and browse to localhost:12345

    kubectl port-forward daemonset/alloy 12345:12345

    The Components tab shows live throughput per loki.* block. A green loki.write.nightlamp means deliveries are succeeding; red surfaces the upstream status.


Promtail → Alloy migration table

Field-by-field mapping for teams converting an existing Promtail config. Keep the labels, the DSN, and the opt-in tag; change only the component shapes.

  • clients[]loki.write "X" { endpoint { url=… headers={…} } }
  • scrape_configs[].kubernetes_sd_configsdiscovery.kubernetes "X" { role="pod" }
  • relabel_configs[]discovery.relabel "X" { rule { ... } } (one rule per relabel)
  • pipeline_stages[]loki.process "X" { stage.drop|json|labels ... }
  • Promtail's __path__ magic relabel is implicit in loki.source.kubernetes; you don't write it.