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.
36 lines
1.2 KiB
Docker
36 lines
1.2 KiB
Docker
# Trip Plan — production image
|
|
# node:20-slim (Debian/glibc) so better-sqlite3 prebuilt binaries load correctly.
|
|
FROM node:20-slim
|
|
|
|
ENV NODE_ENV=production \
|
|
PORT=3000 \
|
|
DATA_DIR=/app/data
|
|
|
|
WORKDIR /app
|
|
|
|
# Install production dependencies first for better layer caching.
|
|
COPY package*.json ./
|
|
RUN npm ci --omit=dev && npm cache clean --force
|
|
|
|
# Application code.
|
|
COPY src/ ./src/
|
|
COPY public/ ./public/
|
|
|
|
# Data directory for the SQLite file (volume-mounted in compose).
|
|
# Owned by the unprivileged `node` user that ships with the base image.
|
|
RUN mkdir -p /app/data && chown -R node:node /app
|
|
|
|
# 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
|
|
|
|
# Any HTTP response (including 401) from the API means the server is up.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
|
CMD node -e "fetch('http://localhost:3000/api/auth/me').then(()=>process.exit(0)).catch(()=>process.exit(1))"
|
|
|
|
CMD ["node", "src/server/index.js"]
|