-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathDockerfile
More file actions
144 lines (117 loc) · 6.08 KB
/
Dockerfile
File metadata and controls
144 lines (117 loc) · 6.08 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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# =============================================================================
# Cornerstone - Multi-stage Docker build
# =============================================================================
# Stage 1 (app-builder): Runs on the BUILD HOST's native arch to avoid
# QEMU emulation. Installs pure-JS deps and builds everything that produces
# platform-independent output: shared types (tsc), client (webpack), and
# server (tsc). No native addons needed — better-sqlite3 is skipped via
# --ignore-scripts.
# Stage 2 (deps): Runs on the TARGET arch to install production deps with
# native addons. better-sqlite3 v12+ ships prebuilt binaries for
# linuxmusl-arm64, so no compilation tools are needed — prebuild-install
# downloads the correct binary during postinstall.
# Stage 3 (production): Minimal runtime image, no npm/build tools/shell.
# =============================================================================
# Standard build: docker build -t cornerstone .
# =============================================================================
# ---------------------------------------------------------------------------
# Stage 1: App builder (native arch — no QEMU)
# ---------------------------------------------------------------------------
# $BUILDPLATFORM resolves to the Docker host's native architecture (e.g.
# linux/amd64 on GitHub Actions), so webpack and tsc run without QEMU
# emulation. All build output is platform-independent JS/CSS/HTML.
FROM --platform=$BUILDPLATFORM dhi.io/node:24-alpine3.23-dev AS app-builder
WORKDIR /app
# Copy package files for dependency installation
COPY package.json package-lock.json ./
COPY shared/package.json shared/
COPY server/package.json server/
COPY client/package.json client/
# Install all dependencies, skipping postinstall scripts. This avoids
# compiling better-sqlite3 (the only native addon) — it's not needed for
# any build step (tsc/webpack produce platform-independent output).
RUN --mount=type=cache,target=/root/.npm npm ci --ignore-scripts
# Stamp the release version into package.json (webpack's DefinePlugin reads
# this to embed __APP_VERSION__ in the client bundle).
ARG APP_VERSION=0.0.0-dev
RUN npm pkg set "version=${APP_VERSION}"
# Copy all source (shared, client, server)
COPY tsconfig.base.json ./
COPY shared/ shared/
COPY client/ client/
COPY server/ server/
# Build everything: shared types (tsc) → client (webpack) → server (tsc)
# All output is platform-independent JS — safe to run on build host's arch.
RUN npm run build -w shared && npm run build -w client && npm run build -w server
# ---------------------------------------------------------------------------
# Stage 2: Production dependencies (target arch)
# ---------------------------------------------------------------------------
# Runs on target platform so prebuild-install downloads the correct
# architecture's prebuilt binary for better-sqlite3. No build tools needed —
# the prebuild is fetched from GitHub Releases, not compiled.
FROM dhi.io/node:24-alpine3.23-dev AS deps
WORKDIR /app
# Copy package files for dependency installation
COPY package.json package-lock.json ./
COPY shared/package.json shared/
COPY server/package.json server/
COPY client/package.json client/
COPY docs/package.json docs/
# Install production dependencies only. better-sqlite3's postinstall
# (prebuild-install) downloads the matching prebuilt .node binary for the
# target platform — no compilation, no build-base/python3 needed.
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev
# Ensure workspace node_modules directories exist even when npm hoists all
# production dependencies to the root node_modules/. Without this, the
# production stage's COPY --from=deps /app/server/node_modules/ fails with
# "not found" whenever no packages remain workspace-local.
RUN mkdir -p /app/server/node_modules /app/client/node_modules /app/shared/node_modules
# Create backups directory (for copying into production stage)
RUN mkdir -p /backups
# ---------------------------------------------------------------------------
# Stage 3: Production (no shell — exec form only)
# ---------------------------------------------------------------------------
FROM dhi.io/node:24-alpine3.23 AS production
# Create data directory (WORKDIR creates intermediate dirs without needing shell)
WORKDIR /app/data
WORKDIR /app
# Copy runtime libraries needed by native addons (better-sqlite3 requires libgcc/libstdc++)
COPY --from=deps /usr/lib/libgcc_s.so.1 /usr/lib/
COPY --from=deps /usr/lib/libstdc++.so.6* /usr/lib/
# Copy package files (needed for workspace resolution)
COPY package.json ./
COPY shared/package.json shared/
COPY server/package.json server/
COPY client/package.json client/
COPY docs/package.json docs/
# Copy production node_modules from deps (npm hoists most deps to root,
# but some may remain in workspace-specific node_modules due to version constraints)
COPY --from=deps /app/node_modules/ node_modules/
COPY --from=deps /app/server/node_modules/ server/node_modules/
# Copy built artifacts from app-builder
COPY --from=app-builder /app/shared/dist/ shared/dist/
COPY --from=app-builder /app/server/dist/ server/dist/
COPY --from=app-builder /app/client/dist/ client/dist/
# Copy SQL migration files (tsc does not copy non-TS assets)
COPY --from=app-builder /app/server/src/db/migrations/ server/dist/db/migrations/
# Create backups directory with correct ownership
COPY --from=deps --chown=node:node /backups /backups
# Expose server port
EXPOSE 3000
# SQLite data volume
VOLUME ["/app/data"]
# Backups volume
VOLUME ["/backups"]
# Environment defaults
ENV NODE_ENV=production
ENV PORT=3000
ENV HOST=0.0.0.0
ENV DATABASE_URL=/app/data/cornerstone.db
ENV LOG_LEVEL=info
ENV CURRENCY=EUR
# Health check — exec form required (DHI production image has no /bin/sh)
# Uses /api/health/ready which verifies DB access and password hashing round-trip
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
CMD ["node", "-e", "fetch('http://localhost:3000/api/health/ready').then(r=>{if(!r.ok)throw r.status}).catch(()=>process.exit(1))"]
# Start the server
CMD ["node", "server/dist/server.js"]