diff --git a/backend/Dockerfile b/backend/Dockerfile index f9f5fd0..4172239 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -2,18 +2,23 @@ FROM python:3.12-slim WORKDIR /app -RUN useradd --create-home --shell /usr/sbin/nologin routeforge +RUN apt-get update \ + && apt-get install -y --no-install-recommends gosu \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --create-home --shell /usr/sbin/nologin routeforge COPY pyproject.toml ./ COPY app ./app COPY alembic ./alembic COPY alembic.ini ./alembic.ini +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN pip install --no-cache-dir . \ && mkdir -p /app/data \ - && chown -R routeforge:routeforge /app + && chown -R routeforge:routeforge /app \ + && chmod +x /usr/local/bin/docker-entrypoint.sh -USER routeforge +ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 8000 diff --git a/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh new file mode 100755 index 0000000..6d2c6e8 --- /dev/null +++ b/backend/docker-entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +mkdir -p /app/data +chown -R routeforge:routeforge /app/data + +exec gosu routeforge "$@" diff --git a/docs/operations/logging.md b/docs/operations/logging.md index 564e94f..a86ce07 100644 --- a/docs/operations/logging.md +++ b/docs/operations/logging.md @@ -24,3 +24,4 @@ docker compose -f docker-compose.prod.yml logs -f postgres - Check Postgres health and credentials (`DATABASE_URL`). - Validate `CORS_ORIGINS` and `VITE_API_URL` alignment. - Inspect retry/fallback diagnostics for upstream RIPEstat outages. +- If you see `sqlite3.OperationalError: attempt to write a readonly database` in dev/compose mode, check volume ownership on `/app/data` and rebuild/restart with the fixed backend image that normalizes volume permissions at startup. diff --git a/docs/operations/security.md b/docs/operations/security.md index 9156458..8a8bc8b 100644 --- a/docs/operations/security.md +++ b/docs/operations/security.md @@ -34,11 +34,19 @@ Frontend nginx sets baseline headers: ## Container hardening +- Backend container starts through an entrypoint as root only long enough to normalize `/app/data` ownership, then drops privileges to non-root user `routeforge` for runtime. + - Backend container runs as non-root user `routeforge`. - Backend image keeps only runtime-relevant files. - Frontend image is multi-stage (build + runtime). - Nginx container still runs with default upstream behavior; strict non-root nginx runtime can be added later as a dedicated hardening step. +## SQLite volume permissions (Docker Compose) + +In the standard `docker-compose.yml` setup with SQLite, the database file is stored at `/app/data/routeforge.db` via the named volume mount `routeforge_data:/app/data`. + +The backend entrypoint ensures `/app/data` is writable for the non-root runtime user `routeforge` on container start. This prevents SQLite write failures such as `sqlite3.OperationalError: attempt to write a readonly database` when a mounted volume is root-owned. + ## What RouteForge does not do - no ROA creation