feat: add containerized FastAPI health check

This commit is contained in:
bot
2026-08-11 14:44:59 +03:00
parent fea8ca89f8
commit f7e668d692
10 changed files with 167 additions and 2 deletions
-1
View File
@@ -10,5 +10,4 @@ __pycache__
data
downloads
docs
tests
*.pyc
+35
View File
@@ -0,0 +1,35 @@
FROM python:3.12-slim-bookworm AS base
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
WORKDIR /app
RUN groupadd --gid 10001 app \
&& useradd --uid 10001 --gid app --no-create-home --shell /usr/sbin/nologin app
COPY requirements.txt ./
RUN pip install --no-cache-dir --requirement requirements.txt
FROM base AS test
COPY requirements-dev.txt pyproject.toml ./
RUN pip install --no-cache-dir --requirement requirements-dev.txt
COPY --chown=app:app app ./app
COPY --chown=app:app tests ./tests
USER app
CMD ["pytest"]
FROM base AS runtime
COPY --chown=app:app app ./app
USER app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"]
+35 -1
View File
@@ -49,7 +49,41 @@ Each stage should produce a small, working, reviewable commit.
## Local development
Local setup instructions will be added with the first application container.
The current milestone is a minimal FastAPI application running in a hardened
local container. Docker publishes it only on the PC's loopback interface, so it
is not exposed to other devices on the LAN.
Build and start it:
```bash
docker compose up --build -d
```
Check its status and logs:
```bash
docker compose ps
docker compose logs --tail=50 web
```
Open <http://127.0.0.1:8000> or request the health endpoint:
```bash
curl --fail http://127.0.0.1:8000/health
```
Run the automated tests in an ephemeral container:
```bash
docker compose run --rm --build test
```
Stop the application:
```bash
docker compose down
```
Runtime data, downloaded media, local environment files, and secrets must not
be committed to Git.
+1
View File
@@ -0,0 +1 @@
"""Media Ingest application package."""
+18
View File
@@ -0,0 +1,18 @@
from fastapi import FastAPI
app = FastAPI(
title="Media Ingest",
description="Private broadcast media-ingest service.",
version="0.1.0",
)
@app.get("/", tags=["system"])
def root() -> dict[str, str]:
return {"name": "Media Ingest", "status": "running"}
@app.get("/health", tags=["system"])
def health() -> dict[str, str]:
"""Report whether the application process can serve requests."""
return {"status": "healthy"}
+45
View File
@@ -0,0 +1,45 @@
services:
web:
build:
context: .
target: runtime
image: media-ingest:local
init: true
ports:
- "127.0.0.1:8000:8000"
healthcheck:
test:
- CMD
- python
- -c
- >-
import urllib.request;
urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
restart: unless-stopped
test:
profiles:
- test
build:
context: .
target: test
image: media-ingest-test:local
init: true
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
+9
View File
@@ -0,0 +1,9 @@
[tool.pytest.ini_options]
addopts = "-ra"
cache_dir = "/tmp/pytest-cache"
pythonpath = ["."]
testpaths = ["tests"]
[tool.ruff]
target-version = "py312"
line-length = 100
+3
View File
@@ -0,0 +1,3 @@
-r requirements.txt
httpx2==2.7.0
pytest==9.1.1
+2
View File
@@ -0,0 +1,2 @@
fastapi==0.139.2
uvicorn==0.51.0
+19
View File
@@ -0,0 +1,19 @@
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health_returns_healthy() -> None:
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "healthy"}
def test_root_identifies_application() -> None:
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"name": "Media Ingest", "status": "running"}