🐍 pyoci
Build OCI container images from Python projects — no Docker required
A native, Docker-free container image builder for Python, inspired by .NET's PublishContainer. Create production-ready OCI images using pure Python, without Dockerfiles or Docker daemon.
🎯 Why This Exists
Today, containerizing Python applications requires:
- Writing and maintaining Dockerfiles
- Installing Docker Desktop or Docker Engine
- Understanding Docker-specific concepts and commands
- Managing multi-stage builds for dependencies
pyoci provides a simpler path:
pyoci build
That's it. No Dockerfile. No Docker daemon. Just pure Python creating OCI-compliant container images.
This mirrors the elegant developer experience that .NET provides with its SDK's native container publishing — now available for Python.
🚀 Quick Start
Installation
# Install the core CLI package from this workspace
pip install -e packages/pyoci
# Using uv (faster)
uv pip install -e packages/pyoci
Build Your First Image
# Simple build (auto-detects Python version and base image)
pyoci build --tag myapp:latest
# Build with custom base image and dependencies
pyoci build \
--tag myapp:v1 \
--base-image python:3.12-slim \
--include-deps
# Build FastAPI app (auto-detected, entrypoint configured)
pyoci build --tag api:latest --context ./my-fastapi-app
# Build with SBOM for security compliance
pyoci build \
--tag myapp:v1 \
--sbom spdx \
--config pyoci.toml
# Build and push to registry
pyoci build --tag ghcr.io/user/myapp:v1 --push
# Build for different platform (e.g., amd64 from ARM Mac)
pyoci build --tag myapp:latest --platform linux/amd64
# Dry-run to preview (verbose mode)
pyoci build --tag test:latest --dry-run --verbose
# Remove old output before rebuilding into the same directory
pyoci build --tag test:latest --clean-output-dir
Output
Creates a complete OCI image layout at dist/image/:
dist/image/
├── index.json # OCI index (manifest list)
├── oci-layout # Version marker
├── blobs/sha256/
│ ├── <manifest-digest> # Manifest blob
│ ├── <config-digest> # Config blob
│ └── <layer-digest> # Application layer (tar)
└── refs/tags/
└── <tag-name> # Tag reference
Testing Locally
Important: pyoci creates OCI-compliant image layouts, not Docker-specific images. To test locally:
Option 1: Use Skopeo (Recommended)
# Install skopeo
brew install skopeo # macOS
# OR: sudo apt-get install skopeo (Ubuntu)
# Copy OCI layout to Docker
skopeo copy oci:dist/image docker-daemon:myapp:latest
docker run -p 8000:8000 myapp:latest
Option 2: Use Podman (Native OCI Support)
# Install Podman
brew install podman # macOS
# Run directly from OCI layout
podman run --rm -p 8000:8000 oci:dist/image:myapp
Option 3: Push to Registry (Production)
# Build and push (works with any container runtime)
pyoci build --tag ghcr.io/user/myapp:latest --push
# Pull and run with Docker or Podman
docker pull ghcr.io/user/myapp:latest
docker run -p 8000:8000 ghcr.io/user/myapp:latest
See Local Development Guide for detailed testing instructions.
✨ Features
Current Capabilities
Foundation & Registry:
- ✅ Zero Docker dependencies — Pure Python implementation
- ✅ Auto-detects Python project structure — Finds
src/,app/, entry points - ✅ Infers entrypoints — Reads
pyproject.tomlscripts, falls back topython -m - ✅ Creates OCI-compliant images — Complete OCI image layout v1
- ✅ Command-line interface — Simple
pyoci buildworkflow - ✅ Programmatic API — Use as a library in your tools
- ✅ Registry push support — Push to GHCR, ACR, Docker Hub via Registry v2 API
- ✅ Blob existence checks — Skip uploading layers that already exist
- ✅ Progress reporting — Visual feedback during push operations
- ✅ Multi-provider authentication — GitHub tokens, Docker config, Azure CLI, env vars
- ✅ OAuth2 token exchange — Automatic bearer token flow with Www-Authenticate
- ✅ Credential auto-discovery — Tries multiple auth sources automatically
- ✅ Layer caching — Content-addressable storage with LRU eviction
- ✅ Cache invalidation — Detects file changes via mtime + size checks
- ✅ Fast incremental builds — Reuses unchanged layers from cache
Base Images & Dependencies:
- ✅ Smart base image detection — Auto-selects Python base image from
requires-pythonin pyproject.toml - ✅ Base image support — Build on top of
python:3.11-slim,python:3.12-slim, distroless, etc. - ✅ Layer merging — Combines base image layers with application layers
- ✅ Config inheritance — Merges env vars, labels, working dir from base images
- ✅ Dependency packaging — Include pip packages from venv or requirements.txt
- ✅ Distroless detection — Auto-handles shell-less base images
Production Features:
- ✅ Framework auto-detection — FastAPI, Flask, Django automatically configured
- ✅ Configuration files — Load settings from
pyoci.toml - ✅ SBOM generation — Create SPDX 2.3 or CycloneDX 1.4 security manifests
- ✅ Reproducible builds — Deterministic layer creation with fixed timestamps
- ✅ Cross-platform builds — Build linux/amd64 from ARM, linux/arm64 from x86, etc.
- ✅ Platform auto-selection — Pulls correct architecture variant from multi-arch base images
- ✅ Verbose logging — Detailed build progress with
--verbose - ✅ Dry-run mode — Preview builds with
--dry-run
Toolchain integrations:
- ✅ Hatch (using
hatch-oci) - ✅ Poetry (using
poetry-oci)
📖 How It Works
Architecture Overview
cli.py (entry point)
└─> builder.py (orchestrates build)
├─> config.py (build configuration)
├─> project.py (Python project introspection)
├─> oci.py (OCI spec structs)
└─> fs_utils.py (file system helpers)
Build Process
- Project Discovery — Reads
pyproject.toml, detects entry points and structure - File Collection — Gathers source files based on auto-detected or configured paths
- Layer Creation — Packs files into tar archive with correct
/app/prefixes - OCI Generation — Creates manifest and config JSON per OCI Image Spec v1
- Output — Writes the OCI image layout to disk and optionally pushes it when
--pushis set
🧩 Programmatic Usage
Use as a library in your Python tools:
from pyoci.config import BuildConfig
from pyoci.builder import ImageBuilder
config = BuildConfig(
tag="myapp:latest",
context_dir="/path/to/app",
env={"ENV": "production"},
include_paths=["src/", "pyproject.toml"],
)
builder = ImageBuilder(config)
builder.build() # Creates dist/image/
Perfect for integration with:
- Azure Developer CLI (azd) — Custom build strategies (docs)
- GitHub Actions — Automated CI/CD workflows (docs)
- Poetry/Hatch — Build plugins in
packages/ - AI agents — Copilot, MCP servers, automated scaffolding
🔌 Integrations
pyoci integrates seamlessly with popular Python tools:
Poetry Plugin
poetry self add poetry-oci
poetry build-container --tag myapp:latest --push
Hatch Plugin
pip install hatch-oci
hatch build # Builds both wheel and container
GitHub Actions
jobs:
build:
uses: willena/pyoci/.github/workflows/pyoci.yml@main
with:
tag: ghcr.io/${{ github.repository }}:latest
push: true
Azure Developer CLI
# azure.yaml
hooks:
build:
run: pyoci build --tag ${SERVICE_IMAGE_NAME} --push
🎓 Configuration
Auto-Detection (Zero Config)
By default, pyoci auto-detects:
- Base image: Python version from
requires-pythoninpyproject.toml(e.g.,>=3.11→python:3.11-slim) - Entry point: First
[project.scripts]entry inpyproject.toml - Include paths:
src/,app/, or<package>/dirs +pyproject.toml,requirements.txt - Working directory:
/app/ - Architecture:
linux/amd64
Explicit Configuration
# Full configuration with all options
pyoci build \
--tag myapp:v1.2.3 \
--context /my/project \
--base-image python:3.11-slim \
--include-deps \
--workdir /app \
--env KEY=value \
--env ANOTHER=value \
--platform linux/amd64 \
--sbom cyclonedx \
--config pyoci.toml \
--verbose \
--push \
--no-cache
# Build for ARM64 (e.g., for AWS Graviton, Apple Silicon containers)
pyoci build \
--tag myapp:arm64 \
--platform linux/arm64 \
--push
Base Image & Dependencies:
--base-image IMAGE— Base image to build on (auto-detected fromrequires-pythonif not specified, e.g.,python:3.11-slim)--include-deps— Package dependencies from venv or requirements.txt
Container Metadata:
--workdir PATH— Set the container working directory--env KEY=VALUE— Add environment variables to the image--label KEY=VALUE— Add OCI labels to the image
Caching Options:
--no-cache— Disable layer caching, force full rebuild--cache-dir PATH— Custom cache directory (default:~/.pyoci/cache)
Production Features:
--config FILE— Load settings frompyoci.toml--sbom FORMAT— Generate SBOM (spdxorcyclonedx)--platform PLATFORM— Target platform (e.g.,linux/arm64)--verbose/-v— Detailed build progress--dry-run— Preview build without creating artifacts--clean-output-dir/--no-clean-output-dir— Control whether the output directory is deleted before building--no-reproducible— Disable deterministic builds
The cache automatically:
- Reuses unchanged layers across builds (content-addressable by SHA256)
- Invalidates on file content changes (mtime + size checks)
- Evicts old entries using LRU when size limit reached (default: 5GB)
Python API
from pyoci.config import BuildConfig
from pyoci.builder import ImageBuilder
config = BuildConfig(
tag="myapp:latest",
context_dir=".",
base_image="python:3.11-slim", # Optional: auto-detected if omitted
include_deps=True,
workdir="/app",
env={"DEBUG": "false", "ENV": "production"},
labels={"version": "1.0", "maintainer": "team@example.com"},
include_paths=["src/", "lib/", "pyproject.toml"],
entrypoint=["python", "-m", "myapp"],
generate_sbom="spdx",
reproducible=True,
verbose=True,
)
builder = ImageBuilder(config)
builder.build()
Configuration File (pyoci.toml)
[build]
base_image = "python:3.11-slim"
workdir = "/app"
include_deps = true
reproducible = true
clean_output_dir = false
[build.labels]
maintainer = "team@example.com"
version = "1.0.0"
[build.env]
PORT = "8080"
ENV = "production"
DEBUG = "false"
Use the image tag itself or --registry at build time to control the push target.
🎯 Design Goals
1. Native Python Experience
Container building should feel like a native Python operation, not a Docker side quest.
2. Zero External Dependencies
No Docker daemon, CLI tools, or system packages required. Pure Python stdlib + OCI specs.
3. Language-Integrated
Understand Python projects natively — entry points, modules, dependencies, project structure.
4. AI-Friendly API
Simple, programmable interface for agentic workflows and Copilot-generated scaffolding.
5. Cross-Platform & Daemonless
Works in GitHub Codespaces, Dev Box, locked-down environments — anywhere Python runs.
🤝 Why This Matters
For Python Developers
- Simpler workflow than Dockerfiles
- No Docker Desktop licensing concerns
- Faster onboarding for containerization
For Microsoft & Azure
- Unified multi-language container story (aligns with .NET, Java/Jib)
- Enables Dockerless Azure Developer CLI workflows
- First-class integration with Copilot and agentic systems
- Better dev experience in Codespaces and cloud dev environments
For the Python Ecosystem
- A modern, standards-based approach to container builds
- Foundation for Poetry, Hatch, and other tool integrations
- Opens new possibilities for Python in cloud-native environments
🔬 Current Limitations
Known limitations and future enhancements:
- Framework detection — Supports FastAPI, Flask, Django only (easy to extend)
- SBOM scope — Python packages only; doesn't parse OS packages from base images
Note: Cross-platform builds pull the correct architecture variant from multi-platform base images and generate proper OCI metadata. For Python applications (interpreted language), no actual cross-compilation is needed.
🛠️ Development
Prerequisites
- Python 3.11+ (uses
tomllib) - No other dependencies — pure stdlib
Install for Development
git clone https://github.com/microsoft/pyoci.git
cd pyoci
uv sync --all-packages --group dev
# Format the whole workspace
uv run ruff format .
Workspace Packaging
From the repository root, uv now treats packages/* as a workspace.
# Inspect the workspace packages
uv workspace list
# Build every publishable package into ./dist
uv build --all-packages --out-dir dist
# Publish all built artifacts to PyPI
uv publish --index pypi dist/*
# Or publish to TestPyPI first
uv publish --index testpypi dist/*
Test a Build
# Create a test project
mkdir test_app && cd test_app
echo 'print("Hello from container!")' > app.py
cat > pyproject.toml << EOF
[project]
name = "test-app"
version = "0.1.0"
EOF
# Build it
pyoci build --tag test:latest --verbose
# Test with skopeo + Docker
skopeo copy oci:dist/image docker-daemon:test:latest
docker run test:latest
# Or test with Podman (native OCI support)
podman run oci:dist/image:test
Code Style
This codebase uses ultra-minimalist, compact Python:
- Semicolons for simple multi-statement lines
- No docstrings on trivial functions
- Aggressive use of pathlib and comprehensions
- Dataclasses over dicts for structured data
This style is intentional for the experimental phase.
📚 Resources
Documentation
- Local Development Guide — Complete guide for using pyoci locally
- Azure Developer CLI Integration — Deploy to Azure with azd
- GitHub Actions Guide — Automate builds in CI/CD
External References
- OCI Image Spec: opencontainers/image-spec
- Docker Registry v2 API: Docker Registry HTTP API V2
- .NET Native Containers: Announcing built-in container support
- Project Tracking: See
IMPLEMENTATION_PLAN.mdfor detailed roadmap
📄 License
MIT License - See LICENSE for details
🙏 Acknowledgments
Inspired by:
- .NET SDK's native container support
- Jib (Java) — Daemonless container builds
- ko (Go) — Simple container images for Go
Built with ❤️ by the Microsoft Python & Azure tooling teams
Metadata
Release files for pyoci 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pyoci-0.1.1.tar.gz | 29.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyoci-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 56.7 kB
Release files / pyoci-0.1.1.tar.gz
| Download URL | pyoci-0.1.1.tar.gz |
|---|---|
| Size | 29.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6376ccae5687c997b690a4c42809b6b7f84e12d5b5381cd4f2cd8e05bb34336d
|
|
BLAKE2b-256 checksum How to use checksums |
45604e2616be33e5c348eece63b72c8b016e99b6ce498fcaafc96e4c6ad38610
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency logRelease files / pyoci-0.1.1-py3-none-any.whl
| Download URL | pyoci-0.1.1-py3-none-any.whl |
|---|---|
| Size | 27.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e8d75750f165e44dccd21a05b31ab92a87620dbc39bac4ae5cfb1cf52cc271bb
|
|
BLAKE2b-256 checksum How to use checksums |
4ee464fc86db83b216558e78380f5db7604e95faf428b29ff9ebe5e347610d92
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency logSecurity for pyoci 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pyoci-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.