Skip to content

Instantly share code, notes, and snippets.

@kamranayub
Last active November 13, 2025 15:53
Show Gist options
  • Select an option

  • Save kamranayub/576c946cd0fff4af39cd91e2cc74e310 to your computer and use it in GitHub Desktop.

Select an option

Save kamranayub/576c946cd0fff4af39cd91e2cc74e310 to your computer and use it in GitHub Desktop.
Debug your Docker build context by displaying a sorted tree of largest directories

Debug Docker Build Context

Goal: Understand what the heck is eating up your large Docker build context! Then, use that to update your .dockerignore and improve the performance of your builds.

Agent Mode 🤖

You can add the DebugDockerContext.prompt.md file to .github/prompts folder and then run the prompt with GitHub Copilot. It will use this first method and analyze your context directory structure to summarize the results.

image

Linux (or WSL)

Based on this SO question, here's what worked for me with Docker Engine v27.4.0 on WSL2 and Windows 11.

In the directory with your .dockerignore:

printf 'FROM busybox\nCOPY . /tmp/build/' | docker build -t test -f- .
docker run --rm -it test du -xhd1 /tmp/build

What this does:

  • Builds a test Docker image (using latest Docker Desktop/Buildkit)
  • Copies the context to /tmp/build inside the image
  • Runs the test image (removing immediately)
  • Outputs the disk usage with folder sizes

It will output something like:

240.0K  /tmp/build/docs
1.2M    /tmp/build/Patched
1020.0K /tmp/build/TestResults
4.0K    /tmp/build/web.blazor
12.0K   /tmp/build/debug
40.0K   /tmp/build/.github
20.0K   /tmp/build/docker
28.3M   /tmp/build/src
228.0K  /tmp/build/Backup
34.8M   /tmp/build

You can then tweak it each time to go deeper if needed, e.g.

docker run --rm -it test du -xhd1 /tmp/build/src

Windows Summary Tree (long method)

This might be nice if you have a huge file structure, which was my case. You also don't need to have Docker Engine installed but you will need Golang and PowerShell.

Use along with docker-build-context-ls to pipe and get a tree of folders sorted by largest size first.

# Run this in your .dockerignore directory
docker-build-context-ls . | .\Summarize-DockerBuildContext.ps1

You should see something like this output:

. [27.96 MB]
  src [21.72 MB]
    Web [15.61 MB]
      Content [11.26 MB]
        videos [8.44 MB]
        images [1.76 MB]
          articles [0.82 MB]
          tiles [0.11 MB]
          sprites [0.03 MB]
          hopscotch [0.01 MB]
          icons [0.01 MB]
          ajax [0 MB]

Credits

Mostly written by ChatGPT with my direction 🤖

I'm sure you can prompt your AI to convert this to Python, Bash, Node.js, whatever you want. Have fun!

mode model tools description
agent
Claude Sonnet 4.5
search/fileSearch
runCommands/runInTerminal
runCommands/getTerminalOutput
fetch
Debug Docker build context

First, fetch DebugDockerBuildContext.md instructions

Important

Only continue if the following is true:

  1. For Windows, WSL2 is enabled and Docker is reachable / enabled from Bash on Windows. Linux containers MUST be enabled.
  2. For Mac/Linux, docker is available

If the above is NOT satisfied, inform the user they should enable those or if they are on Windows, they can manually follow the instructions to use Go and PowerShell method.

If Bash and Docker is available, then:

  1. Find the .dockerignore. If you cannot find one or you find more than one, prompt the user for its location.
  2. Follow the instructions to debug the context, using Bash commands.
  3. Goal: Find the culprits for what is contributing the most to my docker build size
param(
# Root of the build context that the relative paths are based on
[string]$RootPath = "."
)
$rootFull = (Resolve-Path $RootPath).Path
# directory path (".", "src", "src/app", "src/app/images", ...) -> total bytes
$dirSizes = @{}
function Add-SizeToDir([string]$dir, [long]$size) {
if (-not $dirSizes.ContainsKey($dir)) {
$dirSizes[$dir] = 0L
}
$dirSizes[$dir] += $size
}
# --- Phase 1: read file list and accumulate sizes ---
foreach ($line in $input) {
$relRaw = $line.Trim()
if (-not $relRaw) { continue }
# Normalize separators
$relNorm = $relRaw -replace '\\', '/'
$fsRelPath = $relRaw -replace '[\\/]', [IO.Path]::DirectorySeparatorChar
$fullPath = Join-Path $rootFull $fsRelPath
# Only process actual files that exist
if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) { continue }
# Include hidden/system files
$item = Get-Item -LiteralPath $fullPath -Force
$size = [long]$item.Length
$parts = $relNorm -split '/'
# Root aggregate
Add-SizeToDir '.' $size
# Accumulate into each ancestor dir
# e.g. src/app/images/logo.png ->
# "src", "src/app", "src/app/images", "src/app/images/icons"
if ($parts.Length -gt 1) {
for ($i = 0; $i -lt $parts.Length - 1; $i++) {
$dir = ($parts[0..$i] -join '/')
Add-SizeToDir $dir $size
}
}
}
# --- Phase 2: build a real tree (parent -> children) ---
$children = @{}
foreach ($dir in $dirSizes.Keys) {
if ($dir -eq '.') { continue }
# Determine parent
if ($dir.Contains('/')) {
$lastSlash = $dir.LastIndexOf('/')
$parent = $dir.Substring(0, $lastSlash)
} else {
$parent = '.'
}
if (-not $children.ContainsKey($parent)) {
$children[$parent] = @()
}
$children[$parent] += $dir
}
# --- Phase 3: print recursively as a tree ---
function Print-Dir([string]$dir, [int]$depth) {
$indent = ' ' * $depth
if ($dir -eq '.') {
$name = '.'
} else {
$nameParts = $dir -split '/'
$name = $nameParts[-1]
}
$sizeMB = [math]::Round($dirSizes[$dir] / 1MB, 2)
"{0}{1} [{2} MB]" -f $indent, $name, $sizeMB
if ($children.ContainsKey($dir)) {
# Sort children: biggest first, then name for stable output
$sortedChildren = $children[$dir] | Sort-Object `
@{ Expression = { $dirSizes[$_] }; Descending = $true }, `
@{ Expression = { $_ } }
foreach ($child in $sortedChildren) {
Print-Dir -dir $child -depth ($depth + 1)
}
}
}
if ($dirSizes.Count -gt 0) {
Print-Dir '.' 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment