Containerized systems concentrate a lot of secrets in a few places: environment variables, mounted config, sidecar logs, and service-to-service traffic. The logs reflect that.
If you operate Docker or Kubernetes workloads, you will see the same secret shapes repeatedly — and a few that are specific to how containers handle configuration.
How secrets end up in container logs
The most common path is environment variables. Container-based deployments pass credentials through env vars because it is the standard twelve-factor pattern: DATABASE_URL, REDIS_URL, API_KEY, SECRET_KEY. This works well right up until an application prints its configuration on startup for debugging purposes, or until an unhandled exception includes the full environment in its traceback.
A startup log that looks like this is not unusual:
[INFO] Starting app with config:
DATABASE_URL=postgres://appuser:s3cr3tpass@db.internal:5432/prod
REDIS_URL=redis://:anotherpass@cache.internal:6379/0
API_KEY=sk-live-XXXXXXXXXXXXXXXXXXXXXXXX
PORT=8080
That output goes to stdout, gets collected by the container runtime, and ends up wherever your log pipeline sends it.
The second common path is request logging. Applications that log full request and response bodies for debugging will capture whatever is in the Authorization header, any tokens in query strings, and any credentials in request payloads. In development this is often intentional. In production it is frequently left on by accident.
The usual suspects by format
Connection strings are the most information-dense leak: postgres://user:pass@host/db, redis://:pass@host:6379, JDBC strings with embedded credentials. The entire credential is in one line, in a predictable format.
Tokens in headers appear when applications log HTTP traffic: Authorization: Bearer eyJ..., X-API-Key: sk-live-.... These are easy to miss because the header name looks innocuous.
Cloud credentials show up in build and deploy logs: access key IDs in known formats, signed S3 URLs with embedded credentials and expiry, service account tokens from mounted Kubernetes secrets.
Inline JSON payloads are the least predictable. Applications that log request bodies or configuration fragments can expose credentials buried inside nested JSON structures. These are harder to catch with simple patterns because the structure varies.
What makes Kubernetes logs tricky
Stdout and stderr from every container get aggregated and shipped by the kubelet. Multiple workloads share the same log pipeline. When something breaks, debug logging often gets enabled under pressure — and frequently stays on longer than intended.
There are a few Kubernetes-specific patterns worth knowing.
Init container output. Init containers often run configuration setup, database migrations, or secret injection. If they print environment variables or configuration state, that output goes into the same log stream as the main container.
Sidecar logs. Service mesh sidecars, log collectors, and monitoring agents all write to the same aggregated log stream. A misconfigured proxy that logs full request headers will put tokens into the same pipeline as your application logs.
Event objects. kubectl describe pod and kubectl get events include event messages that can contain configuration details. These are not logs in the traditional sense, but teams often copy them into tickets as-is.
A practical workflow for sharing logs outside the cluster
# Extract a time-bounded slice
kubectl logs deploy/myapp --since=15m > raw.log
# Sanitize locally
cat raw.log | logshield scan > sanitized.log
# Verify what was redacted before sharing
cat raw.log | logshield scan --dry-run
# Share only the sanitized file
The raw log stays on your machine. Only the sanitized version leaves your boundary.
For multi-container pods, collect each container separately so you know which stream each line came from:
kubectl logs deploy/myapp -c app --since=15m > app-raw.log
kubectl logs deploy/myapp -c sidecar --since=15m > sidecar-raw.log
cat app-raw.log | logshield scan > app-sanitized.log
cat sidecar-raw.log | logshield scan > sidecar-sanitized.log
Docker-specific patterns
Docker build logs deserve special attention. Multi-stage builds can print build arguments, and if a --build-arg contains a credential, it will appear in the build output:
docker build --build-arg NPM_TOKEN=npm_XXXX . 2>&1 | logshield scan
docker inspect output also commonly contains environment variables in their full form. If you are copying inspect output into a ticket for debugging, treat it the same as log output:
docker inspect <container_id> > inspect.json
cat inspect.json | logshield scan > inspect.sanitized.json
Keep structure intact
Most Kubernetes and Docker logs are structured JSON. Fluentd, Filebeat, and similar collectors emit JSON envelopes around the original log line, and many applications use structured logging libraries that output JSON directly.
If your sanitizer corrupts JSON — by breaking string escapes, removing closing braces, or mangling unicode — you lose the ability to query and filter downstream. The log becomes an unparseable string in your aggregation system.
Redact values. Leave formatting alone.
---
Next: structured JSON logs in more detail, including the classic pitfall of breaking parsers mid-pipeline.