-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
80 lines (64 loc) · 3.19 KB
/
Dockerfile
File metadata and controls
80 lines (64 loc) · 3.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# ============================================================
# ShowPilot-Lite — Server Dockerfile
# ============================================================
# This Dockerfile exists primarily for development/testing. The
# intended deployment for ShowPilot-Lite is the FPP plugin
# installer, not Docker. See README for FPP install steps.
#
# Two-stage build:
# Stage 1 (builder) — installs build tools, compiles native deps
# (better-sqlite3, bcrypt). Heavy.
# Stage 2 (runtime) — Alpine + Node + the compiled node_modules. Lean.
#
# Final image is roughly 130-150 MB. better-sqlite3 needs python3 + make +
# g++ at build time; we don't include them in the runtime image.
# ============================================================
# ---------- Stage 1: build native dependencies ----------
FROM node:20-alpine AS builder
# Build tools for better-sqlite3 / bcrypt native compilation
RUN apk add --no-cache python3 make g++
WORKDIR /build
# Copy only manifests first so this layer caches across rebuilds when
# package.json hasn't changed. (Big deal — recompiling better-sqlite3 takes 30s+)
COPY package.json package-lock.json* ./
# Install production deps. Prefer `npm ci` when a lockfile is present (faster,
# deterministic). Fall back to `npm install` if no lockfile exists yet — this
# lets the Dockerfile work right after `git clone` even if the project hasn't
# committed a package-lock.json. Lockfile users get pinned versions; non-lock
# users get the latest matching package.json semver ranges.
RUN if [ -f package-lock.json ]; then \
npm ci --omit=dev --no-audit --no-fund; \
else \
npm install --omit=dev --no-audit --no-fund; \
fi
# ---------- Stage 2: runtime ----------
FROM node:20-alpine
# Minimal runtime deps. tini is a tiny init that handles signals correctly —
# without it, SIGTERM from `docker stop` doesn't reach the Node process
# cleanly, and the container takes 10s to die instead of 1s.
RUN apk add --no-cache tini
WORKDIR /app
# Copy compiled node_modules from builder
COPY --from=builder /build/node_modules ./node_modules
# Copy the application source
COPY . .
# Don't ship any local config that snuck in (would leak secrets), but DO keep
# config.example.js — server.js falls back to it if config.js isn't mounted,
# making "docker run" smoke-test friendly without compromising security
# (config.example.js has placeholder values, not real secrets).
RUN rm -f config.js
# Data directory for SQLite + cover art uploads.
# Mark as a volume so users remember to mount it.
RUN mkdir -p /app/data /app/data/covers
VOLUME ["/app/data"]
# Run as non-root for safety. node:20-alpine ships a `node` user (UID 1000).
RUN chown -R node:node /app
USER node
# Default port — override with PORT env var in compose if needed
EXPOSE 3100
# Healthcheck — hits the /health endpoint which returns 200 OK once Node is
# accepting connections. The path is /health (not /api/health).
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "require('http').get('http://127.0.0.1:3100/health', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "server.js"]