Skip to content

Commit 6800ebd

Browse files
committed
chore: modernize Docker setup and project configuration
- Refactor Dockerfile for multi-stage builds and security enhancements - Add `Taskfile.yml` for streamlined development workflows - Update `.dockerignore` to exclude unnecessary files and directories - Introduce `dev` optional dependencies group in `pyproject.toml` and `uv.lock` - Cleanup unused entries from `uv.lock` and `pyproject.toml`
1 parent ca3d0cb commit 6800ebd

File tree

6 files changed

+158
-49
lines changed

6 files changed

+158
-49
lines changed

Taskfile.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# https://taskfile.dev
2+
3+
version: '3'
4+
5+
tasks:
6+
dev:frontend:
7+
env:
8+
APP_DEBUG: true
9+
cmd: docker compose --profile dev_frontend up -d --build
10+
dev:backend:
11+
env:
12+
APP_DEBUG: true
13+
cmd: docker compose --profile dev_backend up -d --build

backend/.dockerignore

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,58 @@
1-
files/
1+
# Virtual environments
22
.venv/
3+
venv/
4+
env/
5+
ENV/
6+
7+
# Python cache and compiled files
8+
__pycache__/
9+
*.py[cod]
10+
*$py.class
11+
*.so
12+
.Python
13+
.ruff_cache/
14+
15+
# IDE and editor files
16+
.idea/
17+
.vscode/
18+
.claude/
19+
*.swp
20+
*.swo
21+
*~
22+
.DS_Store
23+
24+
# Git
25+
.git/
26+
.gitignore
27+
.gitattributes
28+
29+
# Documentation
30+
*.md
31+
README.md
32+
docs/
33+
34+
# Testing
35+
.pytest_cache/
36+
.coverage
37+
htmlcov/
38+
39+
# Build artifacts
40+
build/
41+
dist/
42+
*.egg-info/
43+
44+
# Environment and secrets
45+
.env
46+
.env.*
47+
!.env.example
48+
alembic.ini
349
.cleanup.lock
4-
alembic.ini
50+
51+
# Storage directory (mounted as volume)
52+
files/
53+
54+
# Development files
55+
Dockerfile
56+
Dockerfile.*
57+
.dockerignore
58+
docker-compose*.yml

backend/Dockerfile

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,91 @@
1-
FROM python:3.13-slim
1+
# ============================================
2+
# Stage 1: Base - Common Configuration
3+
# ============================================
4+
FROM python:3.13.1-slim AS base
25

3-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
4-
5-
ENV APP_PORT=8000
6-
ENV APP_RELOAD=false
7-
ENV PATH="/root/.local/bin:$PATH"
6+
# Install security updates and minimal runtime dependencies
7+
# Create non-root user (UID 10001 is common convention)
8+
RUN apt-get update && \
9+
apt-get upgrade -y && \
10+
apt-get install -y --no-install-recommends \
11+
ca-certificates \
12+
curl && \
13+
rm -rf /var/lib/apt/lists/* && \
14+
groupadd -g 10001 appuser && \
15+
useradd -u 10001 -g appuser -m -s /bin/bash appuser
816

917
WORKDIR /app
1018

11-
# Copy project files
12-
COPY . .
19+
# ============================================
20+
# Stage 2: Builder - Dependency Installation
21+
# ============================================
22+
FROM base AS builder
23+
24+
# Install build dependencies (only needed during build)
25+
RUN apt-get update && \
26+
apt-get install -y --no-install-recommends \
27+
gcc \
28+
g++ \
29+
libpq-dev && \
30+
rm -rf /var/lib/apt/lists/*
31+
32+
# Copy uv from official image (pinned version for reproducibility)
33+
COPY --from=ghcr.io/astral-sh/uv:0.5.16 /uv /uvx /usr/local/bin/
34+
35+
# Copy dependency files first for optimal layer caching
36+
# Changes to source code won't invalidate this layer
37+
COPY pyproject.toml uv.lock ./
38+
39+
# Install dependencies
40+
# --frozen: Use exact versions from uv.lock (reproducible builds)
41+
# --no-dev: Exclude development dependencies
42+
# --extra migrations: Include migrations group (psycopg2-binary)
43+
# --no-cache: Prevent uv cache bloat in image
44+
RUN uv sync --frozen --no-dev --extra migrations --no-cache
45+
46+
# ============================================
47+
# Stage 3: Runtime - Production Image
48+
# ============================================
49+
FROM base AS runtime
50+
51+
# Copy uv for runtime execution
52+
COPY --from=ghcr.io/astral-sh/uv:0.5.16 /uv /uvx /usr/local/bin/
53+
54+
# Copy installed dependencies from builder stage
55+
# This excludes build tools (gcc, g++, libpq-dev)
56+
COPY --from=builder /app/.venv /app/.venv
57+
58+
# Copy application code
59+
COPY --chown=appuser:appuser . .
60+
61+
# Copy Docker-specific alembic config
62+
COPY --chown=appuser:appuser alembic.docker.ini alembic.ini
63+
64+
# Create files directory and set ownership for entire /app directory
65+
# This ensures appuser can write to .venv and create build artifacts
66+
RUN mkdir -p /app/files && \
67+
chown -R appuser:appuser /app
1368

14-
# Copy alembic config
15-
COPY alembic.docker.ini alembic.ini
69+
# Switch to non-root user for security
70+
USER appuser
1671

17-
# Install system dependencies
18-
RUN apt-get update && apt-get install -y \
19-
curl \
20-
gcc \
21-
&& rm -rf /var/lib/apt/lists/*
72+
# Environment variables
73+
ENV APP_PORT=8000 \
74+
APP_RELOAD=false \
75+
PATH="/app/.venv/bin:$PATH" \
76+
PYTHONUNBUFFERED=1 \
77+
PYTHONDONTWRITEBYTECODE=1
2278

23-
# Install Python dependencies using uv
24-
RUN uv sync --extra migrations
79+
# Expose application port
80+
EXPOSE 8000
2581

26-
EXPOSE $PORT
82+
# Health check using existing /health endpoint
83+
# --interval=30s: Check every 30 seconds
84+
# --timeout=5s: Mark unhealthy if no response in 5s
85+
# --start-period=10s: Grace period for app startup
86+
# --retries=3: Require 3 consecutive failures before marking unhealthy
87+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
88+
CMD curl -f http://localhost:${APP_PORT:-8000}/health || exit 1
2789

90+
# Use exec form for proper signal handling (graceful shutdown)
2891
CMD ["uv", "run", "main.py"]

backend/app/services/paste_service.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def __init__(
4949
self._lock_file: Path = Path(".cleanup.lock")
5050
self._cleanup_service: CleanupService = cleanup_service
5151

52-
5352
async def _read_content(self, paste_path: str) -> str | None:
5453
try:
5554
async with aiofiles.open(paste_path) as f:

backend/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@ dependencies = [
1515
"pydantic-settings>=2.12.0",
1616
"uuid>=1.30",
1717
"aiofiles>=25.1.0",
18-
"setuptools>=68",
19-
"wheel>=0.45.1",
2018
"alembic>=1.17.2",
2119
"slowapi>=0.1.9",
2220
"orjson>=3.11.5",
2321
"aiocache>=0.12.3",
2422
"fastapi-cors>=0.0.6",
25-
"ruff>=0.14.9",
2623
"argon2-cffi>=23.1.0",
2724
]
2825
[project.optional-dependencies]
2926
migrations = [
3027
"psycopg2-binary",
3128
]
29+
dev = [
30+
"ruff>=0.14.9",
31+
]
3232

3333
[tool.uvicorn]
3434
factory = false

backend/uv.lock

Lines changed: 5 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)