Install
$ agentstack add skill-pigfoot-claude-code-hubs-secure-container-build ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Secure Container Build Best Practices
Build secure, minimal container images using security-hardened runtime images and multi-stage builds.
Core Principles
1. Security-First Runtime Images
Use Wolfi glibc-dynamic as the runtime base image:
- Minimal attack surface (no unnecessary packages)
- Built-in CVE-free by design
- Non-root user by default (UID 65532)
- Regular security updates by Chainguard
Production vs Development
# ARG for choosing runtime image tag
ARG RUNTIME_TAG=latest
FROM cgr.dev/chainguard/glibc-dynamic:${RUNTIME_TAG}
latest(default): Production - no shell, most securelatest-dev: Development - includes shell for debugging
Build for debugging
podman build --build-arg RUNTIME_TAG=latest-dev -t myapp:debug .
podman run -it myapp:debug sh # Can exec with shell
2. Multi-stage Builds
Always use multi-stage builds to minimize runtime image size:
- Builder stage: Full build environment (Python/Node.js official images)
- Runtime stage: Minimal Wolfi image with only runtime artifacts
3. Non-root User Configuration
Wolfi images use UID 65532 (nonroot user) by default:
# No need to create user, already exists in Wolfi
USER 65532:65532
# When copying files from builder, set ownership
COPY --from=builder --chown=65532:65532 /app /app
4. Init System (tini)
Wolfi images don't include tini. Copy it from builder:
# Copy tini from builder stage
COPY --from=builder /usr/bin/tini-static /usr/bin/tini
# Use as entrypoint for proper signal handling
ENTRYPOINT ["tini", "--"]
CMD ["your-app"]
Quick Start
Python + uv Project
- Copy Containerfile template:
``bash cp ${CLAUDE_SKILL_DIR}/assets/Containerfile.python-uv ./Containerfile ``
- Update application command:
``dockerfile CMD ["python", "your_app.py"] # Change to your entry point ``
Bun Project
- Copy Containerfile template:
``bash cp ${CLAUDE_SKILL_DIR}/assets/Containerfile.bun ./Containerfile ``
- Update runtime command:
``dockerfile CMD ["bun", "run", "start"] # Change to your script ``
Node.js + pnpm Project
- Copy Containerfile template:
``bash cp ${CLAUDE_SKILL_DIR}/assets/Containerfile.nodejs ./Containerfile ``
- Update entry point:
``dockerfile CMD ["node", "./dist/index.js"] # Change to your compiled output ``
Golang Project
First: Do you need CGO? (packages with C bindings)
Most Go projects don't need CGO. Quick test:
CGO_ENABLED=0 go build .
# Success → Use Containerfile.golang (static, recommended)
# Fails → Use Containerfile.golang-cgo (CGO)
Common CGO packages: mattn/go-sqlite3, git2go/git2go, h2non/bimg
Option A: Pure Go (no CGO) - Recommended
- Copy Containerfile template:
``bash cp ${CLAUDE_SKILL_DIR}/assets/Containerfile.golang ./Containerfile ``
- Update binary name:
``dockerfile CMD ["/app/server"] # Change to your binary name ``
Option B: Requires CGO (SQLite, C libraries)
- Copy CGO template:
``bash cp ${CLAUDE_SKILL_DIR}/assets/Containerfile.golang-cgo ./Containerfile ``
Rust Project
Default: Use glibc template (best compatibility)
- Copy template:
``bash cp ${CLAUDE_SKILL_DIR}/assets/Containerfile.rust ./Containerfile ``
- Update binary name:
``dockerfile CMD ["/app/server"] # Change to your binary name ``
- Optional: Boost performance with mimalloc
Three allocator options (see comments in Containerfile):
- (a) Cargo + mimalloc [Recommended] - Add to Cargo.toml for best performance (50% less memory)
- (b) LD_PRELOAD - Uncomment lines in Containerfile (no code changes, 50% less memory)
- (c) Default malloc - No changes needed (good for most apps)
Why mimalloc? Reduces memory usage by ~50% vs glibc malloc under load. See [allocator comparison](references/allocator-comparison.md) for details vs jemalloc/tcmalloc.
Advanced: Smallest image with musl
If you need the smallest possible image and have no C dependencies:
- Copy musl template:
``bash cp ${CLAUDE_SKILL_DIR}/assets/Containerfile.rust-musl ./Containerfile ``
Warning: musl's allocator is 7-10x slower in multi-threaded workloads. You MUST add mimalloc to Cargo.toml (see Containerfile comments).
Builder Image Selection
Python Projects
FROM docker.io/python:3-slim AS builder
Use official Python slim images for building Python applications with uv.
Bun/Node.js Projects
FROM docker.io/node:lts-slim AS builder
Use official Node.js LTS slim images for building Bun or Node.js applications.
Golang Projects
FROM docker.io/golang:1 AS builder
Use official Golang images for building Go applications. Includes Go toolchain and gcc for CGO support.
Rust Projects
FROM docker.io/rust:slim AS builder
Use official Rust slim images for building Rust applications. Includes rustc, cargo, and rustup for target management.
Build Cache Optimization
Use BuildKit cache mounts to speed up dependency installation:
# Python/uv
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
# Bun
RUN bun install --frozen-lockfile
# pnpm
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile
# Rust/cargo (both registry and target dir)
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/app/target \
cargo build --release
Runtime Image Options
glibc-dynamic (Default)
FROM cgr.dev/chainguard/glibc-dynamic:${RUNTIME_TAG}
- For dynamically linked binaries
- Supports LD_PRELOAD for allocator replacement
- Suitable for: Python, Node.js, Bun, Rust (glibc), Go (CGO)
static (Smallest)
FROM cgr.dev/chainguard/static:${RUNTIME_TAG}
- For statically linked binaries only
- Smallest possible image size (~4MB for Rust)
- No dynamic linker (no LD_PRELOAD)
- Suitable for: Go (pure), Rust (musl)
Debugging Support
All Containerfile templates support both production and debug builds:
Production Build (Default)
podman build -t myapp:latest .
- Uses
cgr.dev/chainguard/glibc-dynamic:latest - No shell (most secure)
- Minimal attack surface
Debug Build
podman build --build-arg RUNTIME_TAG=latest-dev -t myapp:debug .
- Uses
cgr.dev/chainguard/glibc-dynamic:latest-dev - Includes shell and basic tools
- For development/troubleshooting only
Never use debug images in production
For detailed debugging techniques, see references/debugging-containers.md.
Reference Documentation
For detailed information, consult these reference files:
- Security practices: See
references/security-best-practices.md - Dependency management: See
references/dependency-management.md - Debugging containers: See
references/debugging-containers.md - Allocator comparison: See
references/allocator-comparison.md
CI/CD Integration
For GitHub Actions workflows to build multi-arch images, see the github-actions-container-build plugin which provides:
- Matrix build workflow (native ARM64 runners for public repos)
- QEMU workflow (for private repos)
- Podman-based rootless builds with manifest support
Troubleshooting
Common Issues
Build timeout:
- Review Containerfile for inefficient layers
- Enable BuildKit cache mounts
- Consider splitting large builds
Binary not found:
- Verify the COPY command copies the correct path
- Check binary permissions (should be executable)
- For static builds, ensure correct target triple
Container starts but exits immediately:
- Check if tini is properly configured as entrypoint
- Verify the CMD points to the correct binary
- Use debug build to inspect:
podman run -it myapp:debug sh
Local Testing
Test image locally:
podman run --rm -it "$IMAGE:$TAG"
Inspect built image:
podman inspect "$IMAGE:$TAG"
Check image size:
podman images "$IMAGE:$TAG"
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: pigfoot
- Source: pigfoot/claude-code-hubs
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.