Happy Dussehra! May good always win. Happy Diwali! Wishing you a bright year ahead. Merry Christmas! Warm wishes from Vizag. Happy New Year! Here’s to a great year ahead.

DevOps · CNCF daily

Grafana Loki: Log Aggregation That Indexes Labels, Not Log Lines, on Cheap Object Storage

NON CNCF, listed in the CNCF Landscape. An AGPLv3 log database from Grafana Labs that stores compressed log chunks in object storage such as Google Cloud Storage, indexes only a small set of labels, and is queried with LogQL from Grafana, with alerting rules built in.

24 min read

NON CNCF

  • Grafana Loki
  • LogQL
  • Grafana Alloy
  • Fluent Bit
  • Kubernetes
CNCF Landscape page

Grafana Loki is a log aggregation system. Applications and nodes write logs; an agent such as Grafana Alloy or Fluent Bit ships them to Loki; Loki stores them cheaply; and you search them with a query language called LogQL, usually from Grafana. Its central design choice is to index only the labels attached to a log stream (for example namespace, app and cluster) and not the text of each log line. The lines themselves are compressed into chunks and kept in object storage such as Google Cloud Storage or Amazon S3. That makes storage small and cheap and ingestion fast, at the price of queries that scan chunks for the lines you ask about. It is a NON CNCF tool: the CNCF Landscape lists it under Observability and Analysis › Observability, but it is not a CNCF project. It is made by Grafana Labs and has been licensed under AGPLv3 since 2021, having been Apache 2.0 before that. This brief explains Loki from the problem it solves to installing, configuring, securing and operating it on Kubernetes with a Google Cloud Storage bucket, and states plainly that Loki alone does not make logs tamper-proof, so one read is enough to decide whether it fits your platform.

CNCF daily · Sprint 4, post 7 of 15 · Observability

At a glance

Category Observability and Analysis › Observability
CNCF status NON CNCF. A CNCF Landscape item, not a CNCF project
Maker Grafana Labs, with a large open-source community
Written in Go
License AGPLv3 (since April 2021; earlier versions were Apache 2.0). Grafana Enterprise Logs is the commercial distribution
Latest release The 3.7 line (documentation “latest” is v3.7.x). 3.7.4 was published on 22 July 2026 and newer 3.7 patch releases may exist; 3.6.x still receives fixes
Query language LogQL, modelled on PromQL, with log queries and metric queries
Storage A single object store (GCS, S3, Azure Blob Storage and others) for chunks and a TSDB index; filesystem for development only
Deployment modes Single binary, simple scalable (deprecated, to be removed before Loki 4.0) and distributed microservices
Install Helm chart (now maintained in the grafana-community/helm-charts repository), Docker, a Kubernetes operator, or Grafana Cloud
Agents Grafana Alloy, Fluent Bit, Fluentd, OpenTelemetry collectors and others. Promtail is deprecated and was removed in 3.7.3
Tenancy Multi-tenant by default, with the tenant set by the X-Scope-OrgID header
Alerting A built-in ruler evaluates LogQL alerting and recording rules

The problem Loki solves

Every platform produces logs, and the first answer is usually a search engine that indexes every word of every line. That works, and it is expensive: the index can be as large as the data, ingestion needs heavy CPU and memory, and the bill grows with the volume of logs, most of which nobody ever reads. Teams then shorten retention, drop logs, or sample them, and lose the evidence they need on the day something breaks.

Loki bets on a different pattern of use. Most log queries start from a small known set of dimensions: this namespace, this application, this time window. Index those, and scan the rest on demand. The index shrinks to a fraction of the size of the data, ingestion becomes appending compressed chunks to object storage, and a large retention window becomes affordable. The same labels link logs to Prometheus metrics and to traces, so you can move from a graph to the logs of the same pod.

The trade is clear. Loki is excellent at “show me the error lines from this service in the last hour” and at turning logs into metrics, and weaker at needle-in-a-haystack full-text search across everything for a rare term over months, where a full-text engine does better.

Key concepts

  • Stream. A sequence of log lines that share exactly the same set of labels, such as {namespace="shop", app="api", container="server"}. The stream is the unit Loki indexes, buffers and shards.
  • Label. A key-value pair on a stream. Labels are indexed, so every distinct combination of label values creates a new stream. Few, stable labels are the single most important rule.
  • Structured metadata. Key-value pairs stored with each log line but not in the index and not part of the stream identity. It is the place for high-cardinality values such as trace and request IDs, available since Loki 3.0.
  • Chunk. Log lines of one stream, compressed together. Ingesters build them in memory and flush them to object storage.
  • Index. A TSDB index mapping label sets to chunks. Loki 3 defaults to the TSDB index with schema v13.
  • Tenant. An isolated set of data identified by a tenant ID. With multi-tenancy on, each request carries an X-Scope-OrgID header.
  • LogQL. A query language with a stream selector in braces, optional line and label filters, parsers, and metric functions such as rate and count_over_time.
  • Distributor, ingester, querier, query frontend, compactor, index gateway, ruler. The components, described below. They can run as one process or as separate services.
  • Ring. A hash ring, kept in memberlist or a key-value store, that decides which ingester owns which stream.

Architecture

Loki architecture: a log agent pushes streams to the distributor, which replicates them to ingesters; ingesters keep chunks in memory with a write-ahead log and flush chunks and index to object storage; dashboards send LogQL to the query frontend, which splits queries for queriers; queriers read recent logs from ingesters and older logs from object storage; the compactor merges the index and applies retention; the ruler evaluates LogQL alert rules through the queriers and sends alerts to Alertmanager.

Writes and reads use different components that share one object store. Blue is an agent or dashboard, purple a Loki component, grey a background service, teal object storage and orange alerting. Open the zoomable diagram, or download it as SVG or PNG.

Component What it does
Distributor Receives push requests, validates labels and line sizes against limits, hashes each stream and sends it to the right ingesters, replicating it (three copies by default in production setups)
Ingester Holds recent log data in memory, builds compressed chunks, records writes in a write-ahead log, and flushes chunks and index entries to object storage
Query frontend Splits a long query into smaller time ranges, caches results, and queues work fairly across tenants (optionally through a query scheduler)
Querier Runs the LogQL, reading recent data from ingesters and older data from object storage, then merging and deduplicating
Index gateway Serves index lookups so queriers do not each download the index from the object store
Compactor Merges index files, and applies retention and deletion by marking and removing chunks
Ruler Evaluates LogQL alerting and recording rules and sends alerts to Alertmanager
Object storage The system of record for chunks and the index; ingesters are the only state outside it

Deployment modes

  • Single binary (-target=all) runs every component in one process. Use it for development and small installations, with object storage rather than the local filesystem if the data matters.
  • Simple scalable splits the system into read, write and backend targets. It was the common Helm default, and the documentation now says it is being deprecated before Loki 4.0, with the timeline still to be decided. New designs should not start here.
  • Distributed (microservices) runs each component as its own deployment, so you scale and size ingesters, queriers, the compactor and the rest independently. It is the shape to plan production around.

From a log line to a query result

Loki flow in seven steps: the agent pushes a labelled stream to the distributor; the distributor replicates it to ingesters; ingesters flush compressed chunks to object storage; a dashboard sends a LogQL query to the query frontend, which splits it into sub-queries for the querier; the querier reads recent logs from ingesters and older logs from object storage and merges them.

  1. Push. The agent reads a container log, attaches a few labels (cluster, namespace, app) and pushes the stream to the distributor over HTTP.
  2. Replicate. The distributor checks limits and sends the stream to the ingesters that own it on the ring, usually three replicas.
  3. Flush. Each ingester appends lines to the stream’s in-memory chunk and its write-ahead log, and flushes full or idle chunks, with their index entries, to object storage.
  4. Query. Someone runs a LogQL query in Grafana, and the query frontend receives it.
  5. Split. The frontend splits the time range into pieces, uses cached results where it can, and hands the pieces to queriers.
  6. Recent data. Each querier asks ingesters for lines not yet flushed.
  7. Older data. It also reads the index to find matching chunks in object storage, fetches and filters them, and returns the merged result.

Recap: labels choose the chunks, the query filters the lines, and the object store is where the data lives.

Labels, structured metadata and cardinality

This is the part that decides whether a Loki installation works. Every unique combination of label values is a separate stream, each with its own chunks, index entries and memory in an ingester. Loki was built for a small number of long-lived streams, not for high-cardinality labels.

  • Good labels are stable and low-cardinality: cluster, namespace, app, container, environment, level (if the log levels are few).
  • Bad labels have many values: user IDs, request IDs, trace IDs, IP addresses, timestamps, full URLs, pod UIDs. A label with 100,000 values multiplies the stream count by 100,000.
  • Put high-cardinality values in structured metadata, or leave them in the log line and filter at query time with | json | user_id="12345".
  • Limits protect you. By default a tenant is limited to 5,000 active streams globally (max_global_streams_per_user); “active” means a stream that has received lines within chunk_idle_period, 30 minutes by default. Even for hundreds of terabytes a day, the project advises against going beyond about 300,000. Do not raise the limit to make a cardinality problem disappear: it creates huge numbers of tiny chunks and slow queries.
  • Other limits that matter: max_line_size (256 KB by default), ingestion rate limits (a 429 response means the tenant is over its rate), and reject_old_samples_max_age (lines older than it are rejected, which bites when an agent replays old logs).
limits_config:
  allow_structured_metadata: true
  volume_enabled: true
  max_global_streams_per_user: 10000
  max_label_names_per_series: 15
  reject_old_samples_max_age: 168h

Alert on discarded data, because Loki rejects over-limit data and counts it in loki_discarded_samples_total and loki_discarded_bytes_total by reason.

LogQL by example

A LogQL query always starts with a stream selector, and everything after it filters or transforms what the selector chose.

# All lines of one app in one namespace
{namespace="shop", app="api"}

# Lines containing "error" but not "timeout" (cheap line filters first)
{namespace="shop", app="api"} |= "error" != "timeout"

# JSON logs: parse, then filter on a field
{namespace="shop", app="api"} | json | level="error" | status_code >= 500

# logfmt logs and a duration filter
{app="api"} | logfmt | duration > 2s

# Reformat lines for reading
{app="api"} | json | line_format "{{.level}} {{.msg}}"

# Pattern parser (Loki 3 also has a fast pattern match operator)
{app="nginx"} | pattern `<ip> - - [<_>] "<method> <path> <_>" <status> <size>`

Metric queries turn logs into numbers, which is what dashboards and alerts use:

# Error lines per second by app
sum by (app) (rate({namespace="shop"} |= "error" [5m]))

# Error ratio as a percentage
sum(rate({app="api"} | json | level="error" [5m]))
  / sum(rate({app="api"}[5m])) * 100

# p99 latency from a numeric field in the log line
quantile_over_time(0.99,
  {app="api"} | json | __error__="" | unwrap duration_ms [5m]) by (route)

# Top 10 noisiest apps
topk(10, sum by (app) (rate({cluster="prod"}[5m])))

# Fire when an app has stopped logging
absent_over_time({app="api"}[10m])

Two habits keep queries fast: put the most selective labels in the selector, and apply line filters (|=) before parsers (| json), because a filter that discards a line is far cheaper than parsing it.

Install and first run

Docker, one process

For a first look, run the single binary with its sample configuration and a Grafana beside it:

docker run --name loki -d -p 3100:3100 grafana/loki:3.7.4
curl -s localhost:3100/ready

Push a line by hand to see the API shape:

curl -s -H "Content-Type: application/json" -X POST localhost:3100/loki/api/v1/push \
  --data-raw '{"streams":[{"stream":{"app":"demo"},"values":[["'$(date +%s%N)'","hello loki"]]}]}'

Helm on Kubernetes

The chart is now maintained by the community in grafana-community/helm-charts, not in the Loki repository, and its numbering has changed (a major chart version signals breaking changes), so read its upgrade notes:

helm repo add grafana-community https://grafana-community.github.io/helm-charts
helm repo update
helm install loki grafana-community/loki -n logging --create-namespace -f values.yaml

The documentation’s quick start uses a built-in MinIO for storage, but that subchart is deprecated and was scheduled for removal on 31 October 2026, and newer chart versions need an explicit ignoreMinioDeprecation: true to render it. Use it only for a local test. For anything real, use cloud object storage as below.

Production reference design on Kubernetes with Google Cloud Storage

  1. Use distributed mode (deploymentMode: Distributed in the chart) for production, sized by component. Simple scalable still works but is on its way out.
  2. Create separate buckets for chunks, for ruler rules and for admin data, in the same region as the cluster, with uniform bucket-level access and no public access. Do not use the chart’s default bucket names.
  3. Authenticate with GKE Workload Identity, not a downloaded key: bind the Loki Kubernetes service account to a Google service account that has object read and write on those buckets only.
  4. Run three ingesters with replication factor 3, spread across zones, with a persistent volume for each write-ahead log.
  5. Run the compactor as a StatefulSet with a persistent volume for its marker files, because retention depends on it.
  6. Turn on caching (the chart can deploy a chunks cache and a results cache) and an index gateway so queriers do not re-read the index.
  7. Put an authenticating gateway in front of Loki and keep the Loki API off any public network (see Security).
  8. Ship logs with Alloy, shape labels at the agent, and monitor Loki itself.

A values file for this layout. The structure follows the chart’s documented keys; confirm exact names against the values reference for the chart version you install:

deploymentMode: Distributed

loki:
  auth_enabled: true
  schemaConfig:
    configs:
      - from: "2024-04-01"
        store: tsdb
        object_store: gcs
        schema: v13
        index:
          prefix: loki_index_
          period: 24h
  storage:
    type: gcs
    bucketNames:
      chunks: example-loki-chunks
      ruler: example-loki-ruler
      admin: example-loki-admin
  limits_config:
    retention_period: 744h          # 31 days, global default
    max_query_lookback: 744h
    allow_structured_metadata: true
    volume_enabled: true
    max_global_streams_per_user: 10000
  compactor:
    retention_enabled: true
    delete_request_store: gcs
    working_directory: /data/retention
    retention_delete_delay: 2h
  commonConfig:
    replication_factor: 3

serviceAccount:
  annotations:
    iam.gke.io/gcp-service-account: loki@PROJECT_ID.iam.gserviceaccount.com

ingester:
  replicas: 3
  zoneAwareReplication:
    enabled: true
distributor:
  replicas: 3
querier:
  replicas: 3
queryFrontend:
  replicas: 2
queryScheduler:
  replicas: 2
compactor:
  replicas: 1
indexGateway:
  replicas: 2

minio:
  enabled: false

The equivalent plain Loki configuration for the storage and retention parts, which is the shape the project’s own retention documentation shows for a 28-day GCS example:

schema_config:
  configs:
    - from: 2024-04-01
      store: tsdb
      object_store: gcs
      schema: v13
      index:
        prefix: loki_index_
        period: 24h

storage_config:
  tsdb_shipper:
    active_index_directory: /loki/index
    cache_location: /loki/index_cache
  gcs:
    bucket_name: example-loki-chunks

limits_config:
  retention_period: 672h
  max_query_lookback: 672h

compactor:
  working_directory: /data/retention
  delete_request_store: gcs
  retention_enabled: true
  retention_delete_delay: 2h

Retention

Retention is the compactor’s job, and by default it is off, so logs live forever. Rules to remember:

  • compactor.retention_enabled: true and a delete_request_store are both required.
  • Retention only works with a 24-hour index period, which is what the TSDB schema above uses.
  • limits_config.retention_period is the global default, and retention_stream lets you set a different period for streams matched by a label selector, per tenant or globally. Only label matchers are allowed in the selector, not full LogQL.
limits_config:
  retention_period: 744h
  retention_stream:
    - selector: '{namespace="dev"}'
      priority: 1
      period: 168h
    - selector: '{app="audit"}'
      priority: 2
      period: 8760h
  • Chunks are marked, then deleted asynchronously after retention_delete_delay. Set max_query_lookback no longer than the retention period so queries do not look for data that is gone.
  • If you also set a lifecycle rule on the bucket, make its age longer than the Loki retention, or the object store will delete chunks that Loki still thinks exist.
  • The compactor should be stateful with a persistent volume for its marker files; recent releases can also keep markers in object storage with deletion_marker_object_store_prefix.

Shipping logs

Grafana Alloy

Alloy is Grafana’s collector, and its Loki components collect Kubernetes pod logs, reshape labels and write to Loki:

discovery.kubernetes "pods" {
  role = "pod"
}

discovery.relabel "pod_logs" {
  targets = discovery.kubernetes.pods.targets

  rule {
    source_labels = ["__meta_kubernetes_namespace"]
    target_label  = "namespace"
  }
  rule {
    source_labels = ["__meta_kubernetes_pod_label_app"]
    target_label  = "app"
  }
  rule {
    source_labels = ["__meta_kubernetes_pod_container_name"]
    target_label  = "container"
  }
}

loki.source.kubernetes "pod_logs" {
  targets    = discovery.relabel.pod_logs.output
  forward_to = [loki.process.pod_logs.receiver]
}

loki.process "pod_logs" {
  stage.static_labels {
    values = { cluster = "prod-1" }
  }
  forward_to = [loki.write.default.receiver]
}

loki.write "default" {
  endpoint {
    url       = "http://loki-gateway.logging.svc/loki/api/v1/push"
    tenant_id = "platform"
  }
}

Only three or four labels are created (namespace, app, container, plus the static cluster). Pod names and UIDs are deliberately not turned into labels. Alloy can also drop noisy lines, sample high-volume streams and rate-limit before data reaches Loki.

Fluent Bit

If you already run Fluent Bit, which is a sub-project of Fluentd (covered in the Fluentd brief), its built-in loki output sends to the same endpoint:

[OUTPUT]
    Name                   loki
    Match                  kube.*
    Host                   loki-gateway.logging.svc
    Port                   80
    Tenant_ID              platform
    Labels                 job=fluentbit, cluster=prod-1
    Label_Keys             $kubernetes['namespace_name'],$kubernetes['container_name']
    Remove_Keys            kubernetes,stream
    Line_Format            json

Be careful with auto_kubernetes_labels On: it adds every Kubernetes label to the stream labels, which can explode cardinality. Choose labels explicitly with Labels and Label_Keys. Note that Grafana also publishes a separate Go output plugin for Fluent Bit with different options; the built-in one above is the simpler choice.

Promtail, the older Loki agent, was deprecated in Loki 3.0 and removed in 3.7.3, so new installations use Alloy or another collector. OpenTelemetry collectors can also send logs to Loki through its OTLP endpoint.

Alerting with the ruler

The ruler evaluates rules written in LogQL, in the same file format as Prometheus rules, and sends firing alerts to an Alertmanager. Recording rules can write the resulting metric to a Prometheus-compatible store with remote write.

ruler:
  storage:
    type: gcs
    gcs:
      bucket_name: example-loki-ruler
  rule_path: /tmp/rules
  alertmanager_url: http://alertmanager.monitoring.svc:9093
  enable_api: true
  ring:
    kvstore:
      store: memberlist
groups:
  - name: app-logs
    interval: 1m
    rules:
      - alert: HighErrorRatio
        expr: |
          sum by (app) (rate({namespace="shop"} | json | level="error" [5m]))
            /
          sum by (app) (rate({namespace="shop"}[5m])) > 0.05
        for: 10m
        labels:
          severity: page
        annotations:
          summary: "More than 5% of log lines from {{ $labels.app }} are errors"
      - alert: AppStoppedLogging
        expr: absent_over_time({namespace="shop", app="api"}[10m])
        for: 5m
        labels:
          severity: warn
      - record: shop:log_lines:rate1m
        expr: sum by (app) (rate({namespace="shop"}[1m]))

Load rules through the ruler API or the lokitool command, keep the rule files in Git, and prefer alerts on rates and ratios over alerts on a single line. Alerting on a high-cardinality source is expensive, so keep the selector narrow.

Security

  • Loki has no authentication. It trusts the X-Scope-OrgID header. Run an authenticating reverse proxy or gateway (NGINX with basic auth, an OAuth2 proxy, or an API gateway) in front of it, and have that proxy set the tenant header from the authenticated identity. The project’s own documentation warns never to expose the Loki API directly, because a client that can reach it can set the tenant header itself and bypass tenant isolation, including label-based access control.
  • Tenants are the isolation boundary. Use one tenant per team or per environment, with auth_enabled: true (the default). With it off, everything goes to a single tenant called fake. Per-tenant limits and retention can differ.
  • Protect the object store. Whoever can write to the bucket can change or delete your logs, whatever Loki does. Use a dedicated bucket, narrow IAM, Workload Identity, uniform access, audit logging on the bucket, and no human write access.
  • Encrypt in transit between agents, the gateway and Loki, and between Loki and storage, and use customer-managed encryption keys on the bucket if policy requires it.
  • Mind what you log. Loki stores whatever it is sent, including secrets and personal data that applications print. Scrub at the agent, and set retention with data protection rules in mind. A delete API (deletion_mode: filter-and-delete) exists for removing data on request; restrict it.
  • Keep it patched. Loki’s release notes list regular dependency and CVE fixes, so track the 3.7 patch line.

Loki does not make logs tamper-proof

State this plainly in any design review: Loki alone does not provide tamper-proof logs. It stores what it receives and offers no signing, no hash chain, no write-once guarantee and no proof that a line has not been altered, dropped or backdated. A user with access to the bucket, the compactor, the delete API or the ingest path can change what the logs say. Retention deletion, compaction and re-writing of index files are normal operations. If logs must serve as evidence, for audit or for regulation, add controls around Loki:

  • Send a second copy of the relevant logs to a write-once archive. In Google Cloud Storage that means a separate bucket with a retention policy and bucket lock, so objects cannot be deleted or changed until the retention age passes. Do not put the bucket Loki itself uses under a locked retention policy, because Loki’s compactor deletes and rewrites objects, and those operations would be refused.
  • Sign or hash log batches at the source or in the pipeline, and store the digests somewhere separate with its own access control.
  • Separate duties: the people who operate Loki should not be able to delete the archive, and the archive’s audit trail should be reviewed.
  • Restrict clock changes and monitor for gaps with absent_over_time and ingestion-rate alerts.

Monitoring and day-2 operations

Loki exposes Prometheus metrics on every component, and Grafana publishes a meta-monitoring Helm chart for monitoring a Loki deployment, plus the Loki Canary, which writes and reads known lines to measure end-to-end latency and loss. Metrics worth alerting on:

Metric What it tells you
loki_distributor_bytes_received_total, loki_distributor_lines_received_total Ingest volume
loki_discarded_samples_total, loki_discarded_bytes_total Data rejected, by reason (rate limit, stream limit, line too long, too old)
loki_ingester_memory_streams, loki_ingester_memory_chunks Active streams and chunks in ingester memory, the cardinality signal
loki_ingester_chunks_flush_failures_total Flush failures to object storage; should stay at zero
loki_ingester_flush_queue_length Backlog of chunks waiting to flush
loki_request_duration_seconds Request latency by route
loki_query_scheduler_queue_duration_seconds Time queries wait for a querier
loki_ring_members Ring health; unhealthy members should be zero
loki_compactor_apply_retention_last_successful_run_timestamp_seconds The last time retention succeeded
loki_tsdb_build_index_last_successful_timestamp_seconds Index builds are keeping up
loki_ruler_config_last_reload_successful Rules loaded correctly

Operational habits that pay off:

  • Rollouts. Ingesters hold state, so roll them one at a time, zone by zone, and let the write-ahead log replay. Scaling ingesters can temporarily cause stream_limit errors that are not a real cardinality problem; check recent rollouts before raising limits.
  • Query cost. Set limits on query range, parallelism and returned entries, give heavy users their own tenant or limits, and use the query scheduler for fairness.
  • Schema changes. Add a new schema_config period with a future from date instead of editing the old one, so existing data stays readable.
  • Upgrades. Read the upgrade guide for each minor release; Loki 3.0 removed BoltDB and other deprecated options, 3.7.3 removed Promtail, and the Helm chart moved repositories in March 2026.
  • Cost. Object storage and egress dominate. Compress well, keep labels few, drop noisy debug logs at the agent, and set retention per stream.

Usage cases

  • Kubernetes cluster logging with logs next to metrics and traces in Grafana, searched by namespace and app.
  • Logs to metrics. Turn error lines into rates, ratios and latency percentiles for dashboards and alerts.
  • Long retention on a budget. Keep weeks or months of logs in object storage where a full-text index would be too costly.
  • Multi-team platforms. One shared Loki with a tenant, limits and retention per team.
  • Application debugging with labels to narrow the stream and LogQL filters to find the lines.

In the Sprint 4 final project, a pharmaceutical plant’s platform and application logs are collected in Loki on Google Cloud Storage.

Version and project status

Loki began at Grafana Labs in 2018 and reached 1.0 in November 2019. Version 3.0 made TSDB with schema v13 the default, added structured metadata and a native OTLP endpoint, deprecated BoltDB and removed other old options. The 3.x line now includes a Troubleshooting section in the documentation, the pattern match operator, and work on a new object-based storage engine that appears in the changelogs as “dataobj”. The project’s governance and release cadence are documented on the Loki site. Three changes to plan around: Promtail is gone from 3.7.3, simple scalable mode will be deprecated before 4.0, and the Helm chart now lives in the community repository, where its major version numbers mark breaking chart changes. As a Grafana Labs project under AGPLv3, it is open source, but the licence’s network-use terms matter if you modify Loki and offer it as a service, so involve your legal team.

Adoption

Grafana Labs runs Loki as the logs back end of its Grafana Cloud service and sells Grafana Enterprise Logs, and Loki is a component in other distributions: Canonical’s observability stack, for example, documents Loki as one of its components. I did not verify a list of named end-user adopters from an official source, so I name none; ask Grafana Labs or the community for references that fit your size and industry.

Alternatives

Solution Model Best suited to
Elasticsearch or OpenSearch Full-text index on every line Rich full-text search and analytics over logs and documents, with the cost and operation that implies
ClickHouse-based log stores Columnar database for logs Very high-volume log analytics with SQL
VictoriaLogs Log database from the VictoriaMetrics family Teams already using VictoriaMetrics who want a simple log store
Google Cloud Logging Managed logging service Teams that want no log back end to operate on Google Cloud
Splunk and commercial platforms Hosted or licensed log platforms Organisations that want a vendor product with support
Fluentd and Fluent Bit Collectors, not stores Shipping and routing logs to Loki or any other back end

Strengths and limitations

Strengths Limitations
Indexes only labels, so storage and ingestion are cheap Queries scan chunks, so broad full-text searches over long ranges can be slow
Uses object storage for the data, with no large database to run Cardinality discipline is mandatory; bad labels hurt quickly
LogQL turns logs into metrics and alerts, with a built-in ruler No built-in authentication; you must add a gateway
Multi-tenant with per-tenant limits and retention AGPLv3, and deployment modes and charts are changing (simple scalable deprecated, chart moved)
Tight integration with Grafana, Prometheus and traces Not tamper-proof; evidence-grade logging needs extra controls

Recommendation

Adopt for Kubernetes platforms that want affordable, label-based log search next to Prometheus and Grafana. Start in distributed mode on object storage with replication factor 3, define a short, stable label set at the agent, cap streams per tenant, set retention in the compactor, put an authenticating gateway in front, and monitor discards, flush failures and ring health. If you need evidence-grade logs, add a locked archive bucket and source-side signing, because Loki alone will not give you that. If your main need is heavy full-text search across everything, or your licensing rules rule out AGPL, use a different back end.

References: Loki documentation · Loki release notes · Retention · Simple scalable Helm install and chart move · Multi-tenancy · Label-based access control and the security model · Alerting and recording rules · Troubleshoot ingestion · LogQL query examples · Collect Kubernetes logs with Alloy · Fluent Bit Loki output · Loki on GitHub · CNCF Landscape entry

Spotted something wrong or out of date?Suggest a correction. I review every suggestion before changing the page.