The hidden cost of missing license data in container builds
A recent audit of 2,000 public Docker images found that 38% contained at least one GPL‑licensed component without a corresponding notice. For DevOps engineers, that translates into legal exposure that can appear after a release, forcing hot‑fixes and emergency compliance reviews. The problem is not the lack of tools - there are scanners like scancode-toolkit and licensee. The pain point is wiring those scanners into a CI pipeline, extracting human‑readable summaries, and keeping the process repeatable for every daily build. The solution is a tiny bot that pulls the latest image, runs an AI model to generate a concise license report, and pushes the result to a Slack channel. Takeaway: Automate license extraction at image‑creation time; manual checks become a bottleneck. ## Step 1: Build a reproducible base image with the scanner
Start from an official Python image, install scancode-toolkit (v31.2.1) and openai client, then add a thin wrapper script.
FROM python:3.11-slim
RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
# Install scancode-toolkit
RUN curl -sSL https://github.com/nexB/scancode-toolkit/releases/download/v31.2.1/scancode-toolkit-31.2.1.tar.gz \
| tar -xz -C /opt && ln -s /opt/scancode-toolkit-31.2.1/scancode /usr/local/bin/scancode
# Install OpenAI Python SDK
RUN pip install --no-cache-dir openai
COPY summarize.sh /usr/local/bin/summarize.sh
RUN chmod +x /usr/local/bin/summarize.sh
ENTRYPOINT ["summarize.sh"]
yaml
summarize.sh fetches the target image, runs scancode on its filesystem, sends the raw JSON to GPT‑4, and prints a short markdown table. Takeaway: Keep the scanning environment immutable; version pinning eliminates drift. ## Step 2: One‑click deployment with Docker Hub automated builds
Create a public repo on Docker Hub named myorg/license‑bot. Enable Automated Build and point it to the GitHub repository containing the Dockerfile. So configure the build rule to trigger on every push to the main branch. Add a webhook in Docker Hub that calls your CI runner (e.g., GitHub Actions) with the payload:
name: Daily License Scan
on:
schedule:
- cron: "0 4 * * *" # 04:00 UTC daily
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Pull latest image
run: docker pull myorg/license-bot:latest
- name: Run bot
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
run: |
docker run --rm \
-e OPENAI_API_KEY \
-e SLACK_WEBHOOK \
myorg/license-bot:latest \
myorg/target‑service:latest
The workflow pulls the target service image (myorg/target-service:latest), feeds it to the bot, and the bot posts the markdown report to Slack. Takeaway: A scheduled GitHub Action eliminates manual triggers; the entire pipeline runs with a single docker run command. ## Step 3: AI‑driven summarization logic
Inside summarize.sh the critical part is the prompt that converts raw scanner output into a compliance‑ready summary. The script extracts the top‑10 licenses, counts occurrences, and asks GPT‑4 for a brief note on each.
#!/usr/bin/env bash
set -euo pipefail
TARGET_IMAGE=${1:-myorg/target-service:latest}
TMPDIR=$(mktemp -d)
# Export filesystem of target image
docker create --name temp $TARGET_IMAGE
docker export temp | tar -C $TMPDIR -xf -
# Run scancode on exported files
scancode -l --json $TMPDIR/report.json $TMPDIR > /dev/null
# Prepare prompt
PROMPT=$(jq -r '.files[] | select(.license != null) | .license' $TMPDIR/report.json | sort | uniq -c | head -10 | awk '{print $2" " $1}')
PYTHON_SCRIPT=$(cat <<'PY'
import os, json, openai, sys
report = json.load(open('/tmp/report.json'))
licenses = {}
for f in report.get('files', []):
for lic in f.get('license', []):
licenses[lic['key']] = licenses.get(lic['key'], 0) + 1
top = sorted(licenses.items(), key=lambda x: x[1], reverse=True)[:10]
prompt = "Summarize the following Docker image licenses for compliance review. Provide a markdown table with License, Count, and a one‑sentence risk note.\n\n" + "\n".join([f"{l}: {c}" for l,c in top])
resp = openai.ChatCompletion.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
print(resp['choices'][0]['message']['content'])
PY
)
python - <<PY
$PYTHON_SCRIPT
PY
# Post to Slack
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\": \"$(python - <<PY
$PYTHON_SCRIPT
PY
)\"}" $SLACK_WEBHOOK
# Cleanup
docker rm -f temp >/dev/null
rm -rf $TMPDIR
The prompt forces the model to produce a deterministic markdown table, e.g.:
| License | Count | Risk note |
|---|---|---|
| MIT | 124 | Permissive, no attribution required |
| Apache‑2.0 | 37 | Requires NOTICE file |
| GPL‑3.0 | 2 | Must disclose source if distributed |
Takeaway: The AI layer turns noisy scanner data into an actionable report that non‑legal teams can read. ## Step 4: Real‑world validation with three services
-
redis:7-alpine– Scancode flagged 1 MIT component (the Redis source itself) and 3 BSD‑style utilities. The bot posted a two‑line summary; no further action needed. 2.nginx:1.25– DetectedOpenSSL(Apache‑2.0) andzlib(Zlib). The AI added a note about required attribution in the Docker image README. 3.myorg/custom‑api:0.9.4– Found two GPL‑3.0 libraries (libxml2andlibxslt). The bot highlighted the compliance risk, prompting the team to replace them with LGPL alternatives before the next release. Each run completed in under three minutes, far quicker than a manual audit that typically consumes an engineer’s full day. Takeaway: Test the bot on diverse images; the AI consistently surfaces the highest‑risk licenses. ## Keep the bot maintainable - Pin
scancode-toolkitand the OpenAI model version. Upgrading without testing can change output formats. * Store the Slack webhook and OpenAI key in your CI secret store, not in the image. * Add a health‑check endpoint to the container (HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1). These practices prevent the bot from becoming a hidden source of drift. Takeaway: Treat the bot as production code; version control and health checks keep it reliable. Deploy the Dockerfile to your registry, enable the scheduled GitHub Action, and you will receive a daily license summary without touching a terminal. Action for today: Fork the repository, set your Docker Hub and Slack credentials, and run the GitHub Action once to verify that a markdown report lands in your channel.
If this was useful: I packaged a deeper version as Local SEO Audit Prompt Pack for 30‑Minute Walk Radius.
This article was drafted with AI assistance.
Top comments (0)