Skip to content

Commit 486a8af

Browse files
authored
Merge pull request #361 from nanotaboada/feature/docker-compose
feat(container): add Docker Compose support with persistent SQLite volume
2 parents d081ba7 + 3266f36 commit 486a8af

File tree

10 files changed

+102
-26
lines changed

10 files changed

+102
-26
lines changed

.codacy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
exclude_paths:
44
- "assets/**/*"
5-
- "data/**/*"
5+
- "database/**/*"
66
- "models/**/*"
77
- "postman_collections/**/*"
88
- "schemas/**/*"

Dockerfile

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
# - Stage 1: Builder -----------------------------------------------------------
2-
1+
# ------------------------------------------------------------------------------
2+
# Stage 1: Builder
3+
# This stage builds the application and its dependencies.
4+
# ------------------------------------------------------------------------------
35
FROM python:3.13.3-slim-bookworm AS builder
46
WORKDIR /app
57

@@ -12,8 +14,10 @@ RUN apt-get update && \
1214
COPY --chown=root:root --chmod=644 requirements.txt .
1315
RUN pip wheel --no-cache-dir --wheel-dir=/app/wheelhouse -r requirements.txt
1416

15-
# - Stage 2: Runtime -----------------------------------------------------------
16-
17+
# ------------------------------------------------------------------------------
18+
# Stage 2: Runtime
19+
# This stage creates the final, minimal image to run the application.
20+
# ------------------------------------------------------------------------------
1721
FROM python:3.13.3-slim-bookworm AS runtime
1822
WORKDIR /app
1923

@@ -24,25 +28,32 @@ LABEL org.opencontainers.image.licenses="MIT"
2428
LABEL org.opencontainers.image.source="https://github.com/nanotaboada/python-samples-fastapi-restful"
2529

2630
# Copy prebuilt wheels and install dependencies
27-
COPY --from=builder --chown=root:root --chmod=755 /app/wheelhouse /app/wheelhouse
2831
COPY --chown=root:root --chmod=644 requirements.txt .
32+
COPY --from=builder --chown=root:root --chmod=755 /app/wheelhouse /app/wheelhouse
2933
RUN pip install --no-cache-dir --no-index --find-links /app/wheelhouse -r requirements.txt && \
3034
rm -rf /app/wheelhouse
3135

3236
# Copy application code (read-only)
33-
COPY --chown=root:root --chmod=644 main.py ./
34-
COPY --chown=root:root --chmod=755 models ./models
35-
COPY --chown=root:root --chmod=755 routes ./routes
36-
COPY --chown=root:root --chmod=755 schemas ./schemas
37-
COPY --chown=root:root --chmod=755 services ./services
38-
COPY --chown=root:root --chmod=755 data ./data
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
3943

4044
# Copy metadata for GHCR (read-only)
41-
COPY --chown=root:root --chmod=644 README.md ./
42-
COPY --chown=root:root --chmod=755 assets ./assets
45+
COPY --chown=root:root --chmod=644 README.md ./
46+
COPY --chown=root:root --chmod=755 assets ./assets
47+
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 sqlite3-db ./docker-compose
4351

44-
# Create a non-root user for running the app
45-
RUN adduser --system --disabled-password --gecos '' fastapi
52+
# Create non-root user and make volume mount point writable
53+
RUN groupadd --system fastapi && \
54+
adduser --system --ingroup fastapi --disabled-password --gecos '' fastapi && \
55+
mkdir -p /sqlite3-db && \
56+
chown fastapi:fastapi /sqlite3-db
4657

4758
# Drop privileges
4859
USER fastapi
@@ -52,4 +63,5 @@ ENV PYTHONUNBUFFERED=1
5263

5364
EXPOSE 9000
5465

66+
ENTRYPOINT ["./entrypoint.sh"]
5567
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "9000"]

README.md

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,41 @@ http://localhost:9000/docs
4141

4242
![API Documentation](assets/images/swagger.png)
4343

44-
## Docker
44+
## Container
4545

46-
This project includes a multi-stage `Dockerfile` for local development and production builds.
46+
### Docker Compose
4747

48-
### Build the image
48+
This setup uses [Docker Compose](https://docs.docker.com/compose/) to build and run the app and manage a persistent SQLite database stored in a Docker volume.
49+
50+
#### Build the image
51+
52+
```bash
53+
docker compose build
54+
```
55+
56+
#### Start the app
4957

5058
```bash
51-
docker build -t python-samples-fastapi-restful .
59+
docker compose up
5260
```
5361

54-
### Run the container
62+
> On first run, the container copies a pre-seeded SQLite database into a persistent volume
63+
> On subsequent runs, that volume is reused and the data is preserved
64+
65+
#### Stop the app
5566

5667
```bash
57-
docker run -p 9000:9000 python-samples-fastapi-restful:latest
68+
docker compose down
5869
```
5970

71+
#### Optional: database reset
72+
73+
```bash
74+
docker compose down -v
75+
```
76+
77+
> This removes the volume and will reinitialize the database from the built-in seed file the next time you `up`.
78+
6079
## Credits
6180

6281
The solution has been coded using [Visual Studio Code](https://code.visualstudio.com/) with the official [Python](https://marketplace.visualstudio.com/items?itemName=ms-python.python) extension.

codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ comment:
4040
# https://docs.codecov.com/docs/ignoring-paths
4141
ignore:
4242
- "^assets/.*"
43-
- "^data/.*"
43+
- "^database/.*"
4444
- "^models/.*"
4545
- "^postman_collections/.*"
4646
- "^schemas/.*"
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
# ------------------------------------------------------------------------------
44

55
import logging
6+
import os
67
from typing import AsyncGenerator
78
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
89
from sqlalchemy.orm import sessionmaker, declarative_base
910

10-
DATABASE_URL = "sqlite+aiosqlite:///./data/players-sqlite3.db"
11+
database_file_path = os.getenv("DATABASE_FILE_PATH", "./sqlite3-db/players-sqlite3.db")
12+
DATABASE_URL = f"sqlite+aiosqlite:///{database_file_path}"
1113

1214
logger = logging.getLogger("uvicorn")
1315
logging.getLogger("sqlalchemy.engine.Engine").handlers = logger.handlers

docker-compose.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
services:
2+
api:
3+
image: python-samples-fastapi-restful
4+
container_name: fastapi-app
5+
build:
6+
context: .
7+
dockerfile: Dockerfile
8+
ports:
9+
- "9000:9000"
10+
volumes:
11+
- sqlite3-db:/sqlite3-db/
12+
environment:
13+
- PYTHONUNBUFFERED=1
14+
- DATABASE_FILE_PATH=/sqlite3-db/players-sqlite3.db
15+
restart: unless-stopped
16+
17+
volumes:
18+
sqlite3-db:

routes/player_route.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from sqlalchemy.ext.asyncio import AsyncSession
88
from aiocache import SimpleMemoryCache
99

10-
from data.player_database import generate_async_session
10+
from database.player_database import generate_async_session
1111
from models.player_model import PlayerModel
1212
from services import player_service
1313

schemas/player_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# ------------------------------------------------------------------------------
44

55
from sqlalchemy import Column, String, Integer, Boolean
6-
from data.player_database import Base
6+
from database.player_database import Base
77

88

99
class Player(Base):

scripts/entrypoint.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
set -e
3+
4+
IMAGE_DATABASE_FILE_PATH="/app/docker-compose/players-sqlite3.db"
5+
VOLUME_DATABASE_FILE_PATH="/sqlite3-db/players-sqlite3.db"
6+
7+
echo "✔ Starting container..."
8+
9+
if [ ! -f "$VOLUME_DATABASE_FILE_PATH" ]; then
10+
echo "⚠️ No existing database file found in volume."
11+
if [ -f "$IMAGE_DATABASE_FILE_PATH" ]; then
12+
echo "Copying database file to writable volume..."
13+
cp "$IMAGE_DATABASE_FILE_PATH" "$VOLUME_DATABASE_FILE_PATH"
14+
echo "✔ Database initialized at $VOLUME_DATABASE_FILE_PATH"
15+
else
16+
echo "⚠️ Database file missing at $IMAGE_DATABASE_FILE_PATH"
17+
exit 1
18+
fi
19+
else
20+
echo "✔ Existing database file found. Skipping seed copy."
21+
fi
22+
23+
echo "✔ Ready!"
24+
echo "🚀 Launching app..."
25+
exec "$@"

0 commit comments

Comments
 (0)