What are Docker labels and what are they used for?
Docker labels are arbitrary key-value pairs you can attach to almost any Docker object. They are quiet metadata — Docker itself does not act on them, but the ecosystem of tools around Docker reads them constantly. Knowing how to use labels is what unlocks half of the modern Docker tooling.
Theory
TL;DR
- Labels = key-value strings attached to images, containers, volumes, networks, services, and Compose stacks.
- Set in Dockerfile (
LABEL), at run time (--label), in Compose (labels:), or via API. - Docker does not act on labels — tools that watch the Docker API do.
- Standard namespaces:
org.opencontainers.image.*— OCI image annotations (source, version, license).com.docker.compose.*— set automatically by Compose.traefik.*— read by Traefik to configure routing.org.label-schema.*— older convention, still seen in legacy Dockerfiles.
- Filter and query:
docker ps --filter label=env=prod,docker images --filter label=team=platform.
How to set labels
In Dockerfile
LABEL org.opencontainers.image.source=https://github.com/myorg/myapp
LABEL org.opencontainers.image.version=1.2.3
LABEL org.opencontainers.image.description="My production API"
LABEL org.opencontainers.image.licenses=MIT
LABEL maintainer="team@example.com"
# OR multi-line
LABEL org.opencontainers.image.source=https://github.com/myorg/myapp \
org.opencontainers.image.version=1.2.3 \
maintainer="team@example.com"These travel with the image — docker inspect myimg shows them, docker images --filter label=... finds them.
At run time
docker run -d \
--label env=prod \
--label team=platform \
--label cost-center=engineering \
--name api \
myapp:1.0Labels apply to the container instance, not to the image.
In Compose
services:
api:
image: myapp:1.0
labels:
env: prod
team: platform
"traefik.http.routers.api.rule": "Host(`api.example.com`)"
"traefik.http.services.api.loadbalancer.server.port": "3000"Note: dotted keys with values containing dots/special chars need quoting in YAML.
Standard namespaces and what they are for
OCI image labels (standardized)
org.opencontainers.image.created # ISO timestamp of build
org.opencontainers.image.authors # contact info
org.opencontainers.image.url # documentation URL
org.opencontainers.image.documentation # docs URL
org.opencontainers.image.source # source repo URL
org.opencontainers.image.version # image semver
org.opencontainers.image.revision # source git SHA
org.opencontainers.image.vendor # publisher
org.opencontainers.image.licenses # SPDX license expression
org.opencontainers.image.title # human-readable name
org.opencontainers.image.description # short descriptionProduct images on Docker Hub, GHCR, and other registries use these. Setting them is best practice for any image you publish.
Tool-driven labels
# Traefik — auto-routing
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.rule=Host(`api.example.com`)"
- "traefik.http.services.web.loadbalancer.server.port=3000"
# Watchtower — auto-update
labels:
- "com.centurylinklabs.watchtower.enable=true"
- "com.centurylinklabs.watchtower.scope=production"
# autoheal — auto-restart unhealthy
labels:
- "autoheal=true"These tools subscribe to Docker events and read labels to decide what to do.
Compose's automatic labels
Compose adds these to every container it creates:
com.docker.compose.project=<project>
com.docker.compose.service=<service-name>
com.docker.compose.container-number=1
com.docker.compose.config-hash=<sha>
com.docker.compose.oneoff=False
com.docker.compose.version=<compose-version>Filter Compose stacks by these:
docker ps --filter label=com.docker.compose.project=myappFiltering and querying
# Containers with a specific label
docker ps -a --filter label=env=prod
# Containers with a label key (any value)
docker ps -a --filter label=team
# Containers WITHOUT a label
docker ps -a --filter "label!=team=platform"
# Multiple filters (AND)
docker ps -a --filter label=env=prod --filter label=team=api
# Same for images, volumes, networks
docker images --filter label=org.opencontainers.image.version=1.2.3
docker volume ls --filter label=backup=daily
docker network ls --filter label=stack=myappCommon mistakes
Using labels as state
Labels are metadata, not application state. They do not update at runtime; the Docker API treats them as immutable for the object's lifetime. If you need state, use a database or external store.
Putting secrets in labels
# WRONG
labels:
- "db.password=hunter2"Labels appear in docker inspect, ps -a, and image history. Visible to anyone with read access to the daemon. Never put secrets in labels.
Inconsistent label keys
# Bad
service-1: { labels: { env: prod } }
service-2: { labels: { environment: production } }
service-3: { labels: { ENV: PROD } }Filtering becomes a guessing game. Pick one convention (lowercase, no abbreviation, dot-separated for namespacing) and document it.
Forgetting to set OCI labels on published images
Pushing an image to Docker Hub or GHCR without org.opencontainers.image.* labels is a missed opportunity. Registries display them; users rely on them to discover sources, licenses, and version info.
Real-world usage
- Reverse-proxy auto-routing: Traefik labels make a service's routes self-describing. Add a service to the network with the right labels, Traefik picks it up.
- Auto-update: Watchtower watches labeled containers and pulls newer images on a schedule.
- Cost allocation: label every container with
team,cost-center,env. Monitoring stack groups metrics by label for chargeback. - Backup discovery: label volumes with
backup=daily; backup script enumerates by label. - Compliance: label images with
compliance.scan-date,compliance.cve-count; filter for old or vulnerable images. - Multi-tenant cleanup:
docker rm $(docker ps -aq --filter label=tenant=acme)to wipe one tenant's containers.
Follow-up questions
Q: Are labels mutable?
A: No. Labels are set at object creation time and persist for the object's lifetime. To change, recreate the object.
Q: What is the difference between Docker labels and Kubernetes labels?
A: Same idea, different scope. K8s labels are first-class for selecting pods/services into deployments and services. Docker labels are passive metadata read by tools. K8s also has annotations (non-selectable arbitrary metadata) which are closer in spirit to Docker labels.
Q: Can I add labels to a running container?
A: No. Labels are set at create time. To add, recreate the container with --label new-key=value.
Q: What is the recommended format for label keys?
A: Reverse-DNS, all lowercase: com.example.team.env. Standard prefixes: org.opencontainers.*, com.docker.*. Avoid uppercase, spaces, and special characters.
Q: (Senior) How would you use labels for a multi-tenant Docker setup with cost allocation?
A: Mandate labels at admission: every docker run (or Compose service) requires tenant, team, cost-center, env. Enforce via a pre-deploy check or admission controller. Monitoring stack (cAdvisor + Prometheus) sees labels via container_label_* metrics; Grafana dashboards group by tenant. Billing pipeline enumerates docker ps --filter label=tenant=... and aggregates resource usage per tenant. Cleanup automation deletes by label scope. Labels are the glue that connect provisioning, monitoring, and accounting.
Examples
Production-quality Dockerfile with OCI labels
FROM node:22-alpine
LABEL org.opencontainers.image.title="My API"
LABEL org.opencontainers.image.description="Production API for myorg.com"
LABEL org.opencontainers.image.source="https://github.com/myorg/myapp"
LABEL org.opencontainers.image.version="1.2.3"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.authors="Platform Team <platform@myorg.com>"
LABEL org.opencontainers.image.documentation="https://docs.myorg.com/api"
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
USER node
CMD ["node", "server.js"]GitHub's GHCR displays these on the package page. Docker Hub does the same. Anyone pulling the image can find the source, license, and contact.
Compose stack with Traefik labels for auto-routing
services:
traefik:
image: traefik:v3
command:
- --providers.docker
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
ports: ["80:80", "8080:8080"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
api:
image: myorg/api:1.0
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.routers.api.entrypoints=web"
- "traefik.http.services.api.loadbalancer.server.port=3000"
- "team=platform"
- "env=prod"
web:
image: myorg/web:1.0
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.rule=Host(`example.com`)"
- "traefik.http.services.web.loadbalancer.server.port=80"
- "team=platform"
- "env=prod"Traefik discovers services by labels; team/env labels are for filtering and reporting. No nginx config files, no manual route updates.
Filter and clean by label
# Show all prod containers across stacks
docker ps --filter label=env=prod --format 'table {{.Names}}\t{{.Status}}'
# Stop everything labeled "team=experiments"
docker stop $(docker ps -q --filter label=team=experiments)
# Find all images > 1 day old from a specific source
docker images --filter label=org.opencontainers.image.source=https://github.com/myorg/oldproject
# Compose project enumeration
docker ps --filter label=com.docker.compose.project=myapp \
--format '{{.Names}} {{.Status}}'Labels turn docker ps/docker images into a powerful query tool.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.
Comments
No comments yet