This project builds minimal, statically compiled binary-only Docker images from scratch. The images are built using up-to-date base OS images to support the latest async work such as io_uring, but the final concept is to get down to just the application binary without the supporting OS.
scratch_docker_images/
├── build-base/ # Base build environment with latest tools
│ └── Dockerfile # Fedora-based build environment
├── python-static/ # Static Python build
│ └── Dockerfile # Multi-stage Python build
├── workspace/ # Development workspace (created on demand)
├── build.sh # Main build script
├── docker-compose.yml # Development and testing setup
├── Makefile # Convenient build targets
└── README.md # This file
- Latest Fedora Base: Uses the latest Fedora image with current GCC, glibc, and compilation tools
- Static Compilation: Produces fully static binaries with no external dependencies
- Multi-Architecture: Supports both ARM64 and AMD64 architectures
- Optimized Builds: Uses LTO, PGO, and aggressive optimization flags
- Minimal Size: Strips binaries and uses scratch base for final images
- io_uring Support: Includes latest kernel headers for modern async I/O
The first implementation focuses on building a statically compiled Python interpreter:
- Python 3.13.5 (configurable)
- Fully static compilation against standard libraries
- Optimized for size and performance
- Supports modern Python features and async operations
- Docker with buildx support
- Make (optional, for convenience targets)
# Using the build script (cloud build by default)
./build.sh
# Using local build script (optimized for laptop)
./build-local.sh
# Using Make
make build-cloud # Multi-architecture cloud build
make build-local # Single-architecture local build
# Using docker compose
docker compose build
# Test Python version
docker run --rm python-static:latest --version
# Test Python functionality
docker run --rm python-static:latest -c "print('Hello from static Python!')"
# Run benchmarks
make benchmark
The project supports two build modes optimized for different use cases:
- Uses Docker cloud buildx (
cloud-dawsonlp-arm64
) - Multi-architecture builds (ARM64 + AMD64)
- Faster builds with cloud resources
- Ideal for production and CI/CD
# Cloud build (default)
./build.sh
./build.sh --cloud
# Cloud build with push
./build.sh --push dawsonlp/
# Using Make
make build-cloud
make push-cloud
- Uses local buildx builder
- Single architecture (AMD64) for speed
- Optimized for laptop development
- Faster iteration during development
# Local build
./build.sh --local
./build-local.sh
# Local build with push
./build.sh --local --push dawsonlp/
# Using Make
make build-local
make push-local
# Build specific Python version
./build.sh --python-version 3.12.7
# Build and push to registry
./build.sh --push dawsonlp/
# Build for specific platform
./build.sh --platform linux/amd64
# Use local builder
./build.sh --local
# Use cloud builder (default)
./build.sh --cloud
# Show help
./build.sh --help
# Show all available targets
make help
# Build with specific Python version
make build PYTHON_VERSION=3.12.7
# Quick build (single platform)
make quick-build
# Push to registry
make push-all REGISTRY=dawsonlp/
# Analyze image sizes
make size-analysis
# Run development environment
make dev
The base image includes:
- Latest Fedora with current packages
- Complete GCC toolchain with static libraries
- Development tools (cmake, ninja, autotools)
- SSL/TLS libraries (OpenSSL static)
- Compression libraries (zlib, bzip2, xz static)
- Database libraries (SQLite static)
- Latest kernel headers for io_uring support
The Python build process:
- Downloads Python source from python.org
- Configures with static linking and optimizations
- Compiles with LTO and profile-guided optimization
- Strips the binary to minimal size
- Creates a scratch-based runtime image with only the Python binary
- Link-Time Optimization (LTO): Enables cross-module optimizations
- Profile-Guided Optimization (PGO): Uses runtime profiles for optimization
- Static Linking: No external dependencies required
- Section Garbage Collection: Removes unused code sections
- Symbol Stripping: Removes debugging symbols for minimal size
# Start development container
make dev
# Or using docker compose
docker compose up -d build-base
docker compose exec build-base /bin/bash
To add support for a new language:
- Create a new directory (e.g.,
go-static/
,rust-static/
) - Create a Dockerfile that uses
FROM build-base:latest
- Configure static compilation for your language
- Update
build.sh
,Makefile
, anddocker-compose.yml
# Test all images
make test
# Test with docker compose
make test-compose
# Run benchmarks
make benchmark
# Analyze sizes
make size-analysis
PYTHON_VERSION
: Python version to build (default: 3.13.5)REGISTRY
: Docker registry prefix (default: empty)PLATFORM
: Target platforms (default: linux/amd64,linux/arm64)
All Dockerfiles support build arguments for customization:
docker build --build-arg PYTHON_VERSION=3.12.7 python-static/
Expected image sizes (approximate):
build-base
: ~2GB (full development environment)python-static
: ~15-25MB (static Python binary only)
The final runtime images are extremely small because they contain only the statically compiled binary with no OS or libraries.
All images support both ARM64 and AMD64 architectures:
# Build for both architectures
docker buildx build --platform linux/amd64,linux/arm64 --tag python-static:latest python-static/
# Build for specific architecture
docker buildx build --platform linux/arm64 --tag python-static:arm64 python-static/
# Using build script
./build.sh --push dawsonlp/
# Using Make
make push-all REGISTRY=dawsonlp/
The build process is designed to work with CI/CD systems:
# Example GitHub Actions step
- name: Build and push images
run: |
./build.sh --push ${{ secrets.DOCKER_REGISTRY }}/
Advantages:
- No external dependencies
- Extremely small final images
- Better security (no shared library vulnerabilities)
- Consistent behavior across environments
Disadvantages:
- Larger individual binaries
- No shared library memory benefits
- Longer build times
- More complex debugging
The build uses aggressive optimization:
CFLAGS="-O3 -march=native -mtune=native -flto -ffunction-sections -fdata-sections"
LDFLAGS="-Wl,--gc-sections -Wl,--strip-all -static"
-
Build fails with missing dependencies
- Ensure all static libraries are installed in build-base
- Check that PKG_CONFIG_PATH includes static library paths
-
Binary won't run in scratch container
- Verify static linking:
ldd /path/to/binary
should show "not a dynamic executable" - Check for missing static libraries during build
- Verify static linking:
-
Large binary sizes
- Ensure stripping is enabled
- Consider disabling debug symbols
- Review linked libraries for unnecessary dependencies
Build with debug information:
# Modify CFLAGS to include debug info
export CFLAGS="-O3 -g -march=native -mtune=native -flto"
./build.sh
- Fork the repository
- Create a feature branch
- Add your language/runtime support
- Test thoroughly on both architectures
- Update documentation
- Submit a pull request
This project is open source. See individual language implementations for their respective licenses.
- Go static compilation
- Rust static compilation
- C/C++ minimal runtime
- Node.js static build
- Java native image (GraalVM)
- .NET native AOT
- Automated size optimization
- Security scanning integration
- Performance benchmarking suite