Skip to content

Instantly share code, notes, and snippets.

@txoof
Created April 21, 2026 13:57
Show Gist options
  • Select an option

  • Save txoof/6d52e2652d1dd5d82f1b5333377b2ba4 to your computer and use it in GitHub Desktop.

Select an option

Save txoof/6d52e2652d1dd5d82f1b5333377b2ba4 to your computer and use it in GitHub Desktop.
Containerizing Simple Python Package with Docker and GHCR

Containerising a Python Package with Docker and GHCR

A step-by-step guide to containerising a Python package, publishing the image to GitHub Container Registry (GHCR), and making it easy for teammates to run locally.

Prerequisites

  • Docker Desktop installed and running
  • A GitHub account
  • A Python package managed with Poetry
  • A package with at least one entry point (e.g. a FastAPI app)

1. Add a Simple API Entry Point

If your package does not have an API yet, add FastAPI and uvicorn:

poetry add fastapi uvicorn

Add a minimal FastAPI app to your package __init__.py:

from importlib.metadata import version
from fastapi import FastAPI

__version__ = version("your-package-name")

app = FastAPI()

@app.get("/version")
def get_version():
    return {"version": __version__}

Test it locally:

poetry run uvicorn your_package_name:app --reload
curl http://localhost:8000/version

2. Write a Dockerfile

Create a Dockerfile at the project root:

# Base image: slim Python to keep the image size small
FROM python:3.10-slim

# Set the working directory inside the container
WORKDIR /app

# Copy dependency manifest, readme, and source code into the container
COPY pyproject.toml .
COPY README.md .
COPY src/ src/

# Install Poetry, disable virtualenv creation (not needed inside a container),
# and install only production dependencies
RUN pip install poetry && \
    poetry config virtualenvs.create false && \
    poetry install --only main

# Start the app on all interfaces so it is reachable from outside the container
CMD ["uvicorn", "your_package_name:app", "--host", "0.0.0.0", "--port", "8000"]

Build and test locally:

docker build -t your-package-name .
docker run -p 8000:8000 your-package-name
curl http://localhost:8000/version

3. Authenticate with GHCR

Create a GitHub Personal Access Token (PAT)

  1. Go to GitHub -> Settings -> Developer Settings -> Personal Access Tokens -> Tokens (classic)
  2. Click "Generate new token (classic)"
  3. Give it a name, e.g. ghcr-push
  4. Check the write:packages scope
  5. Click "Generate token" and copy it immediately — GitHub will not show it again

Store credentials in a .env file

Create a .env file at the project root:

GITHUB_PAT=your_token_here
GITHUB_USERNAME=your_github_username_in_lowercase

Add .env to .gitignore immediately:

echo ".env" >> .gitignore

Never commit the .env file.

Log in to GHCR

source .env
docker login ghcr.io -u $GITHUB_USERNAME --password $GITHUB_PAT

4. Tag and Push the Image

GHCR requires lowercase repository names. Tag and push:

source .env
docker tag your-package-name ghcr.io/$GITHUB_USERNAME/your-package-name:latest
docker push ghcr.io/$GITHUB_USERNAME/your-package-name:latest

Make the package public

GHCR packages are private by default.

  1. Go to https://github.com/YOUR_USERNAME?tab=packages
  2. Click on your package
  3. Click "Package settings"
  4. Scroll to "Danger Zone" and set visibility to Public

5. Build for Multiple Platforms

Images built on Apple Silicon (ARM) will not run on Intel/AMD machines and vice versa. Build for both platforms in one step using Docker Buildx:

docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 \
  -t ghcr.io/$GITHUB_USERNAME/your-package-name:latest \
  --push .

Platform coverage:

Platform Covers
linux/amd64 Intel Macs, Intel/AMD Linux, Windows
linux/arm64 Apple Silicon Macs (M1/M2/M3/M4)

6. Add Version Tags

Pushing both a version tag and latest is standard practice. latest is a convenience pointer for teammates. The version tag gives you a permanent, reproducible reference for rollbacks.

Read the version from pyproject.toml automatically and push both tags:

VERSION=$(poetry version -s)
docker buildx build --platform linux/amd64,linux/arm64 \
  -t ghcr.io/$GITHUB_USERNAME/your-package-name:$VERSION \
  -t ghcr.io/$GITHUB_USERNAME/your-package-name:latest \
  --push .

7. Automate with a Makefile

Add the following to your Makefile. The VERSION variable is read live from pyproject.toml via poetry version -s.

REGISTRY := ghcr.io/your_github_username
IMAGE := your-package-name
VERSION := $(shell poetry version -s)

docker-build:
	docker buildx build --platform linux/amd64,linux/arm64 \
		-t $(REGISTRY)/$(IMAGE):$(VERSION) \
		-t $(REGISTRY)/$(IMAGE):latest \
		--push .

release-patch:
	poetry version patch
	git add pyproject.toml
	git commit -m "chore: bump version to $$(poetry version -s)"
	git push
	$(MAKE) docker-build

release-minor:
	poetry version minor
	git add pyproject.toml
	git commit -m "chore: bump version to $$(poetry version -s)"
	git push
	$(MAKE) docker-build
	$(MAKE) build
	$(MAKE) upload

release-major:
	poetry version major
	git add pyproject.toml
	git commit -m "chore: bump version to $$(poetry version -s)"
	git push
	$(MAKE) docker-build
	$(MAKE) build
	$(MAKE) upload

Release flow:

make release-patch   # bug fixes — bumps version, commits, pushes container
make release-minor   # new features — also publishes to PyPI
make release-major   # breaking changes — also publishes to PyPI

8. Add a docker-compose.yml for Teammates

Create docker-compose.yml at the project root:

services:
  your-package-name:
    image: ghcr.io/your_github_username/your-package-name:latest
    ports:
      - "8000:8000"

Teammates only need Docker Desktop. They do not need Python, Poetry, or a clone of the repository. They can get the compose file and start the container with two commands:

curl -O https://raw.githubusercontent.com/YOUR_USERNAME/YOUR_REPO/main/docker-compose.yml
docker compose up

To stop:

docker compose down

Summary

Step What it does
Dockerfile Defines how the image is built
GHCR Hosts the image publicly
Buildx Builds for both ARM and AMD64 in one step
Version tags Gives you rollback points
Makefile Automates the full release cycle
docker-compose.yml Lets teammates run the app with one command
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment