Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,26 @@ nosetests.xml
# We exclude everything not strictly required to run the application
.log/
.github/
docker/
docs/
notebooks/
tests/
res/

# Ignore everything in config except for the shared templates
.config/*
!.config/*.example
!.config/*.example.*
!.config/config.dev.toml
!.config/config.*.toml

# Data Handling (Exclude data from image context)
data/

# Repository Metadata
.git/
.gitignore
.gitattributes
.dockerignore
.pre-commit-config.yaml
codecov.yml
LICENSE.md
README.md
pyproject.toml
11 changes: 6 additions & 5 deletions .github/workflows/bootstrap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Checkout
- name: Checkout code
uses: actions/checkout@v6

- name: Collect metadata
Expand All @@ -31,19 +31,17 @@ jobs:
echo "EOF"
} >> "$GITHUB_OUTPUT"

# python package naming
PACKAGE=$(echo "$REPO" | sed -E 's/[- ]+/_/g' | tr 'A-Z' 'a-z') # snake_case for python package
DISTRIBUTION=$(echo "$REPO" | sed -E 's/[_ ]+/-/g' | tr 'A-Z' 'a-z') # kebab-case for python distribution

# store values for later steps
echo "repo=$REPO" >> $GITHUB_OUTPUT
echo "user=$USER" >> $GITHUB_OUTPUT
echo "year=$YEAR" >> $GITHUB_OUTPUT
echo "package=$PACKAGE" >> $GITHUB_OUTPUT
echo "distribution=$DISTRIBUTION" >> $GITHUB_OUTPUT

- name: Install yq
uses: mikefarah/yq@v4.50.1
uses: mikefarah/yq@v4.52.2

- name: Load bootstrap config
id: load_config
Expand Down Expand Up @@ -71,11 +69,14 @@ jobs:
mv src/package_name "src/${{ steps.meta.outputs.package }}"
fi

- name: Update pyproject.toml
- name: Update pyproject.toml & Docker
run: |
sed -i "s/DISTRIBUTION-NAME/${{ steps.meta.outputs.distribution }}/g" pyproject.toml
sed -i "s/package_name/${{ steps.meta.outputs.package }}/g" pyproject.toml
sed -i "s/AUTHOR@EXAMPLE.COM/${{ steps.load_config.outputs.email }}/g" pyproject.toml
sed -i "s/DISTRIBUTION-NAME/${{ steps.meta.outputs.distribution }}/g" docker/Dockerfile
sed -i "s/blueprint-docker/${{ steps.meta.outputs.distribution }}/g" docker/docker-compose.yml


- name: Replace Billboard README with Project README
run: |
Expand Down
46 changes: 45 additions & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ permissions:
packages: write
id-token: write
actions: read
attestations: write

env:
PYTHON_VERSION: "3.12"
Expand Down Expand Up @@ -43,6 +44,49 @@ jobs:
exit 1
fi

# publish-docker:
# name: Build and publish Docker Image
# runs-on: ubuntu-latest
# needs: download-distribution
#
# steps:
# - name: Checkout code
# uses: actions/checkout@v6
#
# - name: Normalize repository name
# id: repo
# run: |
# echo "repo=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
#
# - name: Extract version
# id: meta
# run: |
# VERSION=$(grep '^version =' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
# echo "version=$VERSION" >> $GITHUB_OUTPUT
#
# - name: Login to GitHub Container Registry
# uses: docker/login-action@v3
# with:
# registry: ghcr.io
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
#
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
#
# - name: Build and push
# uses: docker/build-push-action@v6
# with:
# context: .
# file: docker/Dockerfile
# platforms: |
# linux/amd64
# linux/arm64
# push: true
# tags: |
# ghcr.io/${{ steps.repo.outputs.repo }}:latest
# ghcr.io/${{ steps.repo.outputs.repo }}:${{ steps.meta.outputs.version }}
#
# publish-test-pypi:
# name: Publish to TestPyPI
# runs-on: ubuntu-latest
Expand Down Expand Up @@ -72,7 +116,7 @@ jobs:
# github-release:
# name: Create GitHub Release
# runs-on: ubuntu-latest
# needs: [download-distribution, publish-test-pypi]
# needs: [publish-docker, publish-test-pypi]
#
# steps:
# - name: Checkout code
Expand Down
19 changes: 18 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,25 @@ jobs:
name: python-package-distributions-${{ github.sha }}
path: dist/

build-docker:
name: Build Docker Image & Smoke Test
runs-on: ubuntu-latest
needs: test

steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Build Docker image
run: |
docker build -t ${{ github.repository }}:${{ github.sha }} -f docker/Dockerfile .

- name: Smoke Test Container
run: |
docker run --rm ${{ github.repository }}:${{ github.sha }}

smoke-test:
name: Smoke Test Installation
name: Smoke Test Distribution
runs-on: ubuntu-latest
needs: build-distribution
steps:
Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
[use-template-badge]: https://img.shields.io/badge/Use%20this%20template-006222


A **production-ready Python project template** designed to remove setup friction and enforce best practices from the very first commit.
A **production-ready Python project template** designed to reduce repeated setup tasks and enforce best practices from the very first commit.

The **Python Project Blueprint** gives teams a clean, scalable foundation for Python applications and libraries,
with configuration management, structured logging, testing, security scanning, and CI/CD already integrated, so you can focus on building your product, not infrastructure.
with configuration management, structured logging, testing, security scanning, containerization, and CI/CD already integrated, so you can focus more on building your product.


> **Quick Start:** Follow the [Checklist](docs/CHECKLIST.md) to start your next Python project in seconds.
Expand Down Expand Up @@ -84,13 +84,19 @@ By establishing structure, tooling, and automation upfront, it reduces the need
- Secret detection via `detect-secrets`
- Dependency vulnerability scanning with `Snyk`

- **Containerization**
- Multi-stage `Dockerfile` using `uv` for fast builds
- `APP_ENV` configurable at build time and runtime
- Expandable `docker-compose.yml` for multi-container setups
- Publishing images to `GitHub Container Registry`

- **Automated CI/CD**
- `prek` hooks for faster local enforcement
- Workflows with fast dependency resolution using `uv`
- Pull request gatekeeping workflows
- CI verification and packaging
- Automated CD and GitHub Releases
- Dependabot for dependency updates
- CI verification, image building and packaging
- Automated CD with `TestPyPI`, `GHCR` and `GitHub Release`
- Dependency updates with `Dependabot`

- **Governance at Scale**
- Issue & PR templates
Expand Down
37 changes: 37 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1 +1,38 @@
# This Dockerfile was generated for [[PACKAGE_NAME]]

FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder

ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
ENV UV_NO_DEV=1
ENV UV_PYTHON_DOWNLOADS=0

WORKDIR /app

RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --no-install-project

COPY . /app

RUN --mount=type=cache,target=/root/.cache/uv \
uv sync


FROM python:3.12-slim-bookworm

RUN groupadd --system --gid 999 nonroot \
&& useradd --system --gid 999 --uid 999 --create-home nonroot

COPY --from=builder --chown=nonroot:nonroot /app /app

ENV PATH="/app/.venv/bin:$PATH"

USER nonroot

WORKDIR /app

ARG APP_ENV=DEV
ENV APP_ENV=$APP_ENV

CMD ["DISTRIBUTION-NAME"]
11 changes: 11 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
# This docker-compose.yml was generated for [[PACKAGE_NAME]]

services:
app:
image: blueprint-docker-image:local
build:
context: ..
dockerfile: docker/Dockerfile
container_name: blueprint-docker-container
environment:
APP_ENV: DEV
working_dir: /app
4 changes: 3 additions & 1 deletion docs/CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Otherwise, delete the commented-out lines.
- [ ] **USER:** Delete `.gitkeep` in `data/`, `notebooks/` and `res/` if applicable, empty directories will disappear from GitHub.
- [ ] **USER:** Update text in `docs/DOCUMENTATION.md` if applicable.
- [ ] **USER:** Update License type if applicable.
- [ ] **USER:** Update dependencies & [project.optional-dependencies] in `pyproject.toml` if applicable.
- [ ] **USER:** Update dependencies & [dependency-groups] in `pyproject.toml` if applicable.
- [ ] **USER:** Update text in `README.md`.


Expand All @@ -109,6 +109,8 @@ The workflow automatically updates the following sections:

### Setting up `docker/`
- [x] **BOOTSTRAP:** Update [[PACKAGE_NAME]] in `docker/docker-compose.yml` & `docker/Dockerfile`.
- [x] **BOOTSTRAP:** Update `DISTRIBUTION-NAME` in `docker/Dockerfile` to reflect correct command.
- [x] **BOOTSTRAP:** Update `blueprint-docker` in `docker/docker-compose.yml` to reflect correct image & container_name.

### Setting up `docs/`
- [x] **BOOTSTRAP:** Update [[EMAIL]] in `docs/CODE_OF_CONDUCT.md` to reflect correct contact.
Expand Down
Loading