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
37 changes: 37 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: docker

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

# Reuses the Makefile targets so the docker commands live in one place
# and CI validates the same entry points developers use.
- name: Build image
run: make build

- name: Run container
run: make run

# Catches images that build but cannot start (missing runtime deps,
# broken CMD...). Port 7000 matches the Makefile run target.
- name: Smoke test (API responds on /health)
run: |
timeout 30 bash -c 'until curl -sf http://localhost:7000/health > /dev/null; do sleep 1; done'
curl -sf http://localhost:7000/health

- name: Container logs
if: always()
run: make logs || true
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# First, build the application in the `/app` directory.
FROM ghcr.io/astral-sh/uv:0.10.12-python3.14-bookworm-slim AS builder
FROM ghcr.io/astral-sh/uv:0.10.12-python3.14-trixie-slim AS builder
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy

# Disable Python downloads, because we want to use the system interpreter
Expand All @@ -22,9 +22,9 @@ RUN --mount=type=cache,target=/root/.cache/uv \


# Then, use a final image without uv
FROM python:3.14-slim-bookworm
FROM python:3.14-slim-trixie
# It is important to use the image that matches the builder, as the path to the
# Python executable must be the same, e.g., using `python:3.11-slim-bookworm`
# Python executable must be the same, e.g., using `python:3.11-slim-trixie`
# will fail.

# Copy the application from the builder
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.ONESHELL:
.PHONY: install hooks hooks-update ruff ty test test-api docker build run debug attach push
.PHONY: install hooks hooks-update ruff ty test test-api docker build run logs debug attach push

SHELL=/bin/bash
DOCKER_IMG_NAME=ghcr.io/komorebi-ai/python-template
Expand Down Expand Up @@ -50,6 +50,9 @@ run:
[ "$$(docker ps -a | grep $(DOCKER_CONTAINER))" ] && docker stop $(DOCKER_CONTAINER) && docker rm $(DOCKER_CONTAINER)
docker run -d --restart=unless-stopped --name $(DOCKER_CONTAINER) -p 7000:80 $(DOCKER_IMG_NAME)

logs:
docker logs $(DOCKER_CONTAINER)

debug:
docker run -it $(DOCKER_IMG_NAME) /bin/bash

Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dependencies = [
"pydantic",
"uvicorn",
"colorlog>=6.9.0",
# uvicorn needs pyyaml to load the YAML log config (log_conf.yaml)
"pyyaml",
"typer",
]

Expand Down Expand Up @@ -112,8 +114,9 @@ addopts = ["-ra", "--strict-config", "--strict-markers"]
filterwarnings = ["error", "ignore::DeprecationWarning"]

[tool.deptry]
# colorlog is used in log_conf.yaml (not scanned by deptry)
per_rule_ignores = {"DEP002" = ["colorlog"]}
# colorlog is used in log_conf.yaml (not scanned by deptry); pyyaml is used
# by uvicorn to load log_conf.yaml
per_rule_ignores = {"DEP002" = ["colorlog", "pyyaml"]}

[tool.ty.terminal]
error-on-warning = true
Expand Down
10 changes: 10 additions & 0 deletions python_template/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ def read_root() -> dict[str, str]:
return {"python_template-api": f"version {__version__}"}


@app.get("/health")
def health() -> dict[str, str]:
"""Health check for container orchestration and monitoring.

Stable endpoint: keep it when replacing the example endpoints below,
since Docker healthchecks and CI smoke tests rely on it.
"""
return {"status": "ok"}


@app.post("/predict")
def predict(request: Request) -> Response:
"""Mock prediction endpoint."""
Expand Down
Loading