Skip to content

Commit 5c83358

Browse files
committed
feat(routes): add health check endpoint
1 parent 58c5c3b commit 5c83358

File tree

5 files changed

+70
-24
lines changed

5 files changed

+70
-24
lines changed

Dockerfile

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
# This stage builds the application and its dependencies.
44
# ------------------------------------------------------------------------------
55
FROM python:3.13.3-slim-bookworm AS builder
6+
67
WORKDIR /app
78

89
# Install system build tools for packages with native extensions
910
RUN apt-get update && \
1011
apt-get install -y --no-install-recommends build-essential gcc libffi-dev libssl-dev && \
1112
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb
1213

13-
# Pre-build all dependencies into wheels for reproducibility and speed
14+
# Build all dependencies into wheels for reproducibility and speed
1415
COPY --chown=root:root --chmod=644 requirements.txt .
1516
RUN pip wheel --no-cache-dir --wheel-dir=/app/wheelhouse -r requirements.txt
1617

@@ -19,49 +20,59 @@ RUN pip wheel --no-cache-dir --wheel-dir=/app/wheelhouse -r requirements.txt
1920
# This stage creates the final, minimal image to run the application.
2021
# ------------------------------------------------------------------------------
2122
FROM python:3.13.3-slim-bookworm AS runtime
23+
2224
WORKDIR /app
2325

24-
# Metadata labels
26+
# Install curl for health check
27+
RUN apt-get update && apt-get install -y --no-install-recommends curl && \
28+
rm -rf /var/lib/apt/lists/*
29+
30+
# Add metadata labels
2531
LABEL org.opencontainers.image.title="🧪 RESTful API with Python 3 and FastAPI"
2632
LABEL org.opencontainers.image.description="Proof of Concept for a RESTful API made with Python 3 and FastAPI"
2733
LABEL org.opencontainers.image.licenses="MIT"
2834
LABEL org.opencontainers.image.source="https://github.com/nanotaboada/python-samples-fastapi-restful"
2935

30-
# Copy prebuilt wheels and install dependencies
31-
COPY --chown=root:root --chmod=644 requirements.txt .
32-
COPY --from=builder --chown=root:root --chmod=755 /app/wheelhouse /app/wheelhouse
36+
# Copy metadata docs for container registries (e.g.: GitHub Container Registry)
37+
COPY README.md ./
38+
COPY assets ./assets
39+
40+
# Copy pre-built wheels from builder
41+
COPY --from=builder /app/wheelhouse /app/wheelhouse
42+
43+
# Install dependencies
44+
COPY requirements.txt .
3345
RUN pip install --no-cache-dir --no-index --find-links /app/wheelhouse -r requirements.txt && \
3446
rm -rf /app/wheelhouse
3547

36-
# Copy application code (read-only)
37-
COPY --chown=root:root --chmod=644 main.py ./
38-
COPY --chown=root:root --chmod=755 database ./database
39-
COPY --chown=root:root --chmod=755 models ./models
40-
COPY --chown=root:root --chmod=755 routes ./routes
41-
COPY --chown=root:root --chmod=755 schemas ./schemas
42-
COPY --chown=root:root --chmod=755 services ./services
48+
# Copy application source code
49+
COPY main.py ./
50+
COPY database ./database
51+
COPY models ./models
52+
COPY routes ./routes
53+
COPY schemas ./schemas
54+
COPY services ./services
4355

44-
# Copy metadata for GHCR (read-only)
45-
COPY --chown=root:root --chmod=644 README.md ./
46-
COPY --chown=root:root --chmod=755 assets ./assets
56+
# Copy entrypoint script and image-bundled, pre-seeded SQLite database
57+
COPY --chmod=755 scripts/entrypoint.sh ./entrypoint.sh
58+
COPY --chmod=755 scripts/healthcheck.sh ./healthcheck.sh
59+
COPY --chmod=755 storage ./docker-compose
4760

48-
# Copy entrypoint sctipt and SQLite database
49-
COPY --chown=root:root --chmod=755 scripts/entrypoint.sh ./entrypoint.sh
50-
COPY --chown=root:root --chmod=755 storage ./docker-compose
51-
52-
# Create non-root user and make volume mount point writable
61+
# Add non-root user and make volume mount point writable
5362
RUN groupadd --system fastapi && \
5463
adduser --system --ingroup fastapi --disabled-password --gecos '' fastapi && \
5564
mkdir -p /storage && \
5665
chown fastapi:fastapi /storage
5766

67+
ENV PYTHONUNBUFFERED=1
68+
5869
# Drop privileges
5970
USER fastapi
6071

61-
# Logging output immediately
62-
ENV PYTHONUNBUFFERED=1
63-
6472
EXPOSE 9000
6573

74+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
75+
CMD ["./healthcheck.sh"]
76+
6677
ENTRYPOINT ["./entrypoint.sh"]
6778
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "9000"]

main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import logging
77
from typing import AsyncIterator
88
from fastapi import FastAPI
9-
from routes import player_route
9+
from routes import player_route, health_route
1010

1111
# https://github.com/encode/uvicorn/issues/562
1212
UVICORN_LOGGER = "uvicorn.error"
@@ -26,3 +26,4 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]:
2626
version="1.0.0",)
2727

2828
app.include_router(player_route.api_router)
29+
app.include_router(health_route.api_router)

routes/health_route.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# ------------------------------------------------------------------------------
2+
# Route
3+
# ------------------------------------------------------------------------------
4+
5+
from fastapi import APIRouter
6+
7+
8+
api_router = APIRouter()
9+
10+
@api_router.get("/health", tags=["Health"])
11+
async def health_check():
12+
"""
13+
Simple health check endpoint. Returns a JSON response with a single key "status" and value "ok".
14+
"""
15+
return {"status": "ok"}

scripts/healthcheck.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/sh
2+
set -e
3+
4+
# Simple health check using curl
5+
curl --fail http://localhost:9000/health

tests/test_main.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@
77

88
PATH = "/players/"
99

10+
# GET /health/ -----------------------------------------------------------------
11+
12+
def test_given_get_when_request_path_is_health_then_response_status_code_should_be_200_ok(client):
13+
"""
14+
Given GET /health/
15+
when request
16+
then response Status Code should be 200 OK
17+
"""
18+
# Act
19+
response = client.get("/health/")
20+
# Assert
21+
assert response.status_code == 200
22+
assert response.json() == {"status": "ok"}
23+
1024
# GET /players/ ----------------------------------------------------------------
1125

1226

0 commit comments

Comments
 (0)