Fix bind-mount data dir permissions via entrypoint
Docker creates ./data root-owned on the host, but the app runs as the unprivileged node user, causing SQLITE_CANTOPEN on first deploy. New entrypoint starts as root, chowns DATA_DIR, then drops privileges with setpriv. Compose keeps the SQLite database in ./data next to the compose file. Also trims scripts/ and playwright artifacts from the image.
This commit is contained in:
@@ -18,6 +18,8 @@ tests
|
||||
.swarm
|
||||
.mcp.json
|
||||
ruvector.db
|
||||
.playwright-mcp
|
||||
scripts
|
||||
|
||||
# Local env files
|
||||
.env
|
||||
|
||||
+5
-1
@@ -20,7 +20,11 @@ COPY public/ ./public/
|
||||
# Owned by the unprivileged `node` user that ships with the base image.
|
||||
RUN mkdir -p /app/data && chown -R node:node /app
|
||||
|
||||
USER node
|
||||
# The entrypoint starts as root only to chown a bind-mounted DATA_DIR
|
||||
# (Docker creates host dirs root-owned), then drops to `node` via setpriv.
|
||||
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
|
||||
@@ -32,13 +32,17 @@ services:
|
||||
environment:
|
||||
SESSION_SECRET: "<long random string — e.g. openssl rand -hex 32>"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./data:/app/data # database lives next to this compose file
|
||||
# No public port mapping needed when Caddy shares a Docker network with
|
||||
# the app (recommended). For a host-level Caddy, map localhost only:
|
||||
ports:
|
||||
- "127.0.0.1:3000:3000"
|
||||
```
|
||||
|
||||
The entrypoint chowns `./data` to the app's unprivileged `node` user on
|
||||
startup, so a root-created bind-mount directory works out of the box. (Images
|
||||
older than this note need a one-time `sudo chown -R 1000:1000 ./data`.)
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
@@ -108,7 +112,7 @@ npm test # API tests (node:test + supertest)
|
||||
|
||||
## Data
|
||||
|
||||
All state lives in a single SQLite file under `DATA_DIR`. In Docker this is `/app/data`, mounted from `./data` on the host. No external database or services are required (geocoding calls OpenStreetMap's Nominatim, which needs no API key; airport lookups use a bundled offline dataset).
|
||||
All state lives in a single SQLite file under `DATA_DIR`. In Docker this is `/app/data`, bind-mounted from `./data` next to the compose file — back up that directory to back up all trips. No external database or services are required (geocoding calls OpenStreetMap's Nominatim, which needs no API key; airport lookups use a bundled offline dataset).
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
+3
-1
@@ -4,7 +4,9 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
# SQLite database persists on the host across container rebuilds.
|
||||
# SQLite database lives in ./data next to this compose file and
|
||||
# persists across container rebuilds. The entrypoint fixes ownership
|
||||
# automatically (the app itself runs as the unprivileged `node` user).
|
||||
- ./data:/app/data
|
||||
environment:
|
||||
# CHANGE THIS: set a long random string before exposing the app.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Bind-mounted data directories (e.g. ./data next to the compose file) are
|
||||
# created root-owned by Docker, but the app runs as the unprivileged `node`
|
||||
# user. When we start as root: fix ownership of DATA_DIR, then drop
|
||||
# privileges. When started with a custom --user, just run as-is.
|
||||
DATA_DIR="${DATA_DIR:-/app/data}"
|
||||
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
mkdir -p "$DATA_DIR"
|
||||
chown -R node:node "$DATA_DIR"
|
||||
exec setpriv --reuid=node --regid=node --init-groups "$@"
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
Reference in New Issue
Block a user