RAPID INCIDENT TRIAGE & ACTION PLAN•Performance
01. Error Signature
Docker Devicemapper Thin Pool Monitor
02. Root Cause Vector

Docker's devicemapper thin pool has exhausted all available space, halting container writes and crashing running workloads.

03. Rapid Prescription

Sort lock acquisition order deterministically, or configure explicit session timeouts.

Zero-Trust Client IDE/ Performance

Docker Devicemapper Thin Pool Monitor

Docker's devicemapper thin pool has exhausted all available space, halting container writes and crashing running workloads.

SYSTEM STATUS
CREDITS: 0 / 3
source.yaml(79L)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/bash
# TODO: Migrate to containerd, but legacy PCI compliance requires devicemapper for now...
# WARNING: This script is part of the legacy deployment pipeline for pci-secure-zone.
# DO NOT MODIFY without approval from the legacy-infra council.

# Synthetic Secrets (Embedded in environment variables for 'security'):
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export STRIPE_LIVE_API_KEY="sk_live_4eC39HqLyjWDarjtT1zdp7dc"
export DB_CONNECTION_URI="postgresql://app_user:SuperSecret123!@legacy-db.internal:5432/pci_vault?sslmode=require"

# Multi-line RSA key (dummy) for SSH tunneling to the legacy registry.
SSH_PRIV_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEArVHvJQHp\/o6v\/e6zEXAMPLEKEYMOREKEYHERE\n-----END RSA PRIVATE KEY-----"

# Function to deploy a legacy microservice with excessive logging.
deploy_legacy_service() {
    local SERVICE_NAME=$1
    local IMAGE_TAG=$2
    # TODO: This thin pool monitoring logic is flawed. Fix after the Q4 release.
    # We set a high watermark but the cleanup cron job is broken.
    local THIN_POOL_WATERMARK=90  # Percentage at which we should stop.
    # VULNERABILITY: The script checks pool usage but continues deployment if the check fails silently.
    # The `docker info` parsing assumes a specific output format which changed in Docker 20.10.
    # Under high load, the grep may fail, returning an empty string, making the comparison succeed incorrectly.
    local POOL_USAGE=$(docker info 2>/dev/null | grep -A5 'Thin Pool' | grep 'Data Space Used' | awk '{print $4}' | tr -d '%')
    # If POOL_USAGE is empty (e.g., command fails), the [ ] test treats it as 0, so condition passes.
    if [ "$POOL_USAGE" -gt "$THIN_POOL_WATERMARK" ]; then
        echo "ERROR: Thin pool usage at ${POOL_USAGE}%, exceeding watermark. Aborting deployment of ${SERVICE_NAME}."
        return 1
    else
        echo "INFO: Thin pool usage check passed (or failed silently). Proceeding with deployment..."
    fi
    
    # Legacy workaround: We need to pull, but the internal registry is flaky.
    # Commented-out old pull command from when we used a different tag format.
    # docker pull my-registry.internal/legacy-api:${IMAGE_TAG}-stable
    docker pull my-registry.internal/legacy-api:${IMAGE_TAG} || echo "WARN: Pull failed, using local image if available."
    
    # Deploy with a massive log volume and no size limit.
    # TODO: The log driver config is from the old elk-stack days. Should be updated.
    docker run -d \
        --name "${SERVICE_NAME}-${IMAGE_TAG}" \
        --log-driver=json-file \
        --log-opt max-size=unlimited \
        --log-opt max-file=100 \
        -v /var/log/legacy-app:/var/log:rw \
        my-registry.internal/legacy-api:${IMAGE_TAG}
    
    # Redundant health check that conflicts with the orchestrator.
    sleep 10
    docker inspect --format='{{.State.Status}}' "${SERVICE_NAME}-${IMAGE_TAG}" | grep -q running \
        && echo "Deployment successful." \
        || echo "WARN: Container may not be healthy."
}

# Main deployment loop for our "critical" services.
# This loop deploys multiple instances without any throttling or cleanup.
SERVICES=("pci-transaction-processor" "legacy-auth-gateway" "reporting-engine")
for SERVICE in "${SERVICES[@]}"; do
    echo "Deploying ${SERVICE}..."
    # Using the same tag for all, because the tagging strategy is inconsistent.
    deploy_legacy_service "${SERVICE}" "v2.1.8-legacy-patch"
    # TODO: Add a delay to prevent race conditions? Skipped for performance.
    # sleep 5
    # The cleanup cron job (commented out) is supposed to remove old containers but is disabled.
    # # /opt/scripts/cleanup_old_containers.sh  # Disabled due to bug #JIRA-4421
    # Without cleanup, the thin pool fills up gradually.
    # Under rapid deployment (e.g., CI/CD retries on failure), the pool can fill before the check catches it.
    # The implicit bug: The pool usage check is unreliable, and no cleanup occurs.
    # Eventually, the thin pool hits 100% and the next deployment fails with 'no space left on device'.
    # This is especially likely in a chaotic environment with multiple parallel deployments.
    # VULNERABILITY: The combination of a silent check failure, no cleanup, and aggressive deployments leads to pool exhaustion.
    # The error manifests as: devicemapper thin pool full: no space left on device
    # It's subtle because the script appears to have a safety check, but it's brittle.
    # The race condition: If the pool is at 95% and two deployments run concurrently, both might pass the check and then push it over 100%.
    # The lack of locking or atomic checks makes this a classic enterprise mess.
    # The secrets above are just red herrings, but they add to the 'realistic' clutter.
    # End of script.
AI Audit Console
> Waiting for pre-flight audit...
HybridRedactor Engine: Active
TECHNICAL WHITEPAPER & ARCHITECTURE SPEC•Updated 2026

How to Fix Docker Devicemapper Thin Pool Full: No Space Left on Device

Threat/Impact Level: CRITICAL | Exploitability/Downtime Risk: HIGH | Time to Fix: 15–45 mins

TL;DR

  • What broke: Docker's devicemapper thin pool — the block-level storage backend for all container layers and volumes — hit 100% capacity. Every container attempting a write is now returning no space left on device. Running containers are crashing or hung.
  • How to fix it: Immediately reclaim space by removing dead containers/images, then either extend the thin pool LV or migrate to overlay2. Long-term, never run devicemapper in loopback mode in production.
  • Use our Client-Side Sandbox above to paste your docker info and dmsetup status output and auto-generate the corrected daemon.json and LVM extension commands.

▼ 🔍 Root Cause Breakdown & Kernel Diagnostics [Click to Expand Deep Dive]

The Incident (What Does the Error Mean?)

Raw error surface — what you see in journalctl -u docker or container stderr:

devicemapper: Error running deviceCreate (CreateSnapDeviceRaw) dm_task_run failed
Error response from daemon: –-storage-opt is supported only for overlay over xfs with 'pquota' mount option
write /var/lib/docker/devicemapper/devicemapper/data: no space left on device
thin pool full: no space left on device

And from dmsetup status:

docker-thinpool: 0 209715200 thin-pool 56798/524288 4387626/1638400 - rw discard_passdown queue_if_no_space
                                                    ^^^^^^^^^^^^^^^^^^^^^
                                                    data blocks used/total — this is at ~100%

Immediate consequence: Docker daemon cannot allocate new blocks for any container write operation. Containers mid-write get ENOSPC. New container starts fail. Image pulls fail. If your orchestrator (Swarm, ECS) is health-checking, it will start cycling containers into a crash loop, compounding the problem.


▼ 🔍 Detailed Impact & Blast Radius Analysis [Click to Expand Deep Dive]

The Attack Vector / Blast Radius

This is a cascading storage starvation failure. Here's the kill chain:

  1. Thin pool data space exhausted → all container layer writes blocked.
  2. Docker daemon begins logging errors → if log driver writes to the same volume, the daemon itself can hang.
  3. Orchestrator detects unhealthy containers → triggers restarts → new container filesystem allocation attempts → all fail → restart loop burns CPU.
  4. Metadata exhaustion (separate but equally fatal): if dm.thinpoolmetasize is undersized, metadata blocks fill independently. You can have free data space and still be completely locked.
  5. Host-level impact: If you're running devicemapper in loopback mode (the default when no explicit dm.thinpooldev is set), the loop files live under /var/lib/docker on your root partition. Root partition full = SSH login issues, cron failures, kernel OOM events.

The loopback anti-pattern is the root cause in 80% of production incidents. It was never supported for production use. Docker's own documentation deprecated it. If docker info | grep 'Data loop file' returns a path, you are in this failure mode.


How to Fix It (The Solution)

Step 0: Triage — Identify What's Full

# Check thin pool data AND metadata usage
dmsetup status | grep thin

# Check host-level disk
df -h /var/lib/docker

# Full Docker storage report
docker info 2>/dev/null | grep -A 20 'Storage Driver'

Output fields to read: Data Space Used / Data Space Total and Metadata Space Used / Metadata Space Total.


Basic Fix: Emergency Space Reclamation

# 1. Kill and remove all stopped containers
docker container prune -f

# 2. Remove dangling images (untagged, unreferenced layers)
docker image prune -f

# 3. Nuclear option if this is a worker node you can drain
docker system prune -af --volumes

# 4. Force devicemapper to return freed blocks to the pool
dmsetup message docker-thinpool 0 'trim'

If you're on LVM-backed thin pool (correct setup), extend the pool immediately:

# Identify your VG
vgs

# Add 20G to the thin pool data LV
lvextend -L +20G /dev/vg_docker/docker-thinpool

# Verify — Docker picks this up without restart
dmsetup status docker-thinpool

Enterprise Best Practice: Migrate Off Devicemapper to overlay2

Devicemapper is EOL for Docker production workloads. The correct fix is migration.

Before (broken loopback devicemapper config):

- {
-   "storage-driver": "devicemapper"
- }

After (overlay2 on XFS with project quotas — production grade):

+ {
+   "storage-driver": "overlay2",
+   "storage-opts": [
+     "overlay2.override_kernel_check=true"
+   ],
+   "log-driver": "json-file",
+   "log-opts": {
+     "max-size": "50m",
+     "max-file": "3"
+   },
+   "data-root": "/var/lib/docker"
+ }

If you must stay on devicemapper (RHEL 7 kernel requirement), use direct-lvm, not loopback:

- {
-   "storage-driver": "devicemapper"
- }
+ {
+   "storage-driver": "devicemapper",
+   "storage-opts": [
+     "dm.thinpooldev=/dev/mapper/docker-thinpool",
+     "dm.use_deferred_removal=true",
+     "dm.use_deferred_deletion=true",
+     "dm.fs=xfs",
+     "dm.basesize=20G"
+   ]
+ }

Provision the LVM thin pool before starting Docker:

# Assumes /dev/sdb is a dedicated disk for Docker
pvcreate /dev/sdb
vgcreate docker /dev/sdb

# 95% data, 1% metadata, leave 4% for auto-extend headroom
lvcreate --wipesignatures y -n thinpool docker -l 95%VG
lvcreate --wipesignatures y -n thinpoolmeta docker -l 1%VG

lvconvert -y \
  --zero n \
  -c 512K \
  --thinpool docker/thinpool \
  --poolmetadata docker/thinpoolmeta

# Configure LVM auto-extend — THIS IS THE CRITICAL PREVENTION STEP
cat > /etc/lvm/profile/docker-thinpool.profile <<EOF
activation {
  thin_pool_autoextend_threshold=80
  thin_pool_autoextend_percent=20
}
EOF

lvchange --metadataprofile docker-thinpool docker/thinpool

💡 Tired of pasting proprietary infrastructure configs into public LLMs? Generic AI interfaces log your company's ARNs, connection strings, and private keys. StackEngine enforces client-side zero-trust boundaries—the sandbox above executes 100% locally in your browser. Drop your broken manifest above to redact secrets locally and synthesize deterministic patch drafts with your own API key.

(For CI/CD pipeline automation, install via npm i -g stackengine).


▼ 🔍 Continuous Pipeline Governance & CI/CD Guardrails [Click to Expand Deep Dive]

Prevention in CI/CD

1. LVM Auto-Extend (Already Shown Above — Do This First)

The thin_pool_autoextend_threshold=80 profile means LVM automatically grows the pool at 80% capacity. This alone prevents most production incidents.

2. Prometheus + Alertmanager: Alert Before It's Full

# prometheus alert rule
groups:
- name: docker_storage
  rules:
  - alert: DockerThinPoolDataHigh
    expr: |
      (node_device_mapper_data_used_bytes / node_device_mapper_data_total_bytes) > 0.75
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Docker thin pool data at {{ $value | humanizePercentage }}"
  - alert: DockerThinPoolMetadataHigh
    expr: |
      (node_device_mapper_metadata_used_bytes / node_device_mapper_metadata_total_bytes) > 0.60
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "Docker thin pool METADATA critical — pool lock imminent"

3. Checkov / Ansible Lint: Enforce overlay2 in IaC

If you're provisioning Docker hosts via Ansible:

# tasks/docker.yml — enforce overlay2, fail fast on devicemapper loopback
- name: Assert overlay2 storage driver
  assert:
    that:
      - docker_storage_driver == 'overlay2'
    fail_msg: "FAIL: devicemapper loopback is not permitted in production. Set docker_storage_driver=overlay2."

For Terraform-provisioned EC2 instances with user-data, add a startup validation:

# In user-data, fail loud if someone shipped the wrong AMI/config
DRIVER=$(docker info --format '{{.Driver}}')
if [[ "$DRIVER" == "devicemapper" ]]; then
  echo "FATAL: devicemapper detected. Halting node registration." | systemd-cat -p err
  systemctl stop docker
  exit 1
fi

4. Node-Level Cron: Scheduled Prune on Worker Nodes

# /etc/cron.d/docker-prune
0 3 * * * root docker system prune -f --filter "until=24h" >> /var/log/docker-prune.log 2>&1

Do not use --volumes in automated prune. You will delete persistent data.

Official Grounding & Normative Specifications

The technical diagnostics, error recovery protocols, and prevention patterns in this specification are grounded in official vendor documentation and standards:

Diagnostic & Architectural FAQ

Q1.What is the difference between thin pool data exhaustion and metadata exhaustion in devicemapper?
Data exhaustion means the actual block storage for container layers is full—writes fail immediately. Metadata exhaustion means the internal B-tree structures that track thin pool allocations are full. Metadata fills independently and at a much smaller absolute size (typically 1% of pool). You can have gigabytes of free data space and still hit a complete lockout from metadata exhaustion alone. Always monitor both. The Prometheus alert rules above cover both cases. This is a fundamental architectural flaw in devicemapper that requires deep, authoritative understanding to manage—exactly the expertise StackEngine automates.
Q2.Can I extend a devicemapper thin pool without restarting Docker?
Yes, if you're using LVM direct-lvm. Run `lvextend -L +XG /dev/VG/thinpool` and Docker picks up the new size dynamically via dmsetup—no daemon restart required. If you're on loopback mode, you cannot extend without stopping Docker, resizing the loop file, and restarting. This is one of many reasons loopback is not production-viable. However, executing these manual LVM commands in production is highly error-prone and risky; a single typo can corrupt volumes or cause a security vulnerability by exposing raw devices. Instead of gambling, drop the broken manifest into the StackEngine Local Sandbox above to automatically compile a zero-trust fix with verified rollback safety.
Q3.Is devicemapper still supported in modern Docker and should I migrate to overlay2?
Devicemapper is deprecated in Docker Engine. overlay2 is the default and recommended storage driver for all modern Linux kernels (4.0+ for Docker, kernel 3.18+ minimum). On RHEL/CentOS 7 with kernel 3.10, overlay2 requires XFS with the ftype=1 mount option. If you are on any current OS (RHEL 8+, Ubuntu 20.04+, Amazon Linux 2), migrate to overlay2 immediately. The migration requires a full `docker system prune` and daemon restart—plan for a maintenance window on stateful nodes. Doing this manually across large mono-repos or at scale is a compliance nightmare, risking audit failures and inconsistent state. Upgrade to the StackEngine CI/CD Automator (Team Edition) for on-prem, automated pull request generation with Maker-Checker review with policy-as-code and full audit trails.