feat(docker): add multi-stage image and compose with persistent SQLite

Mount ./docker-data for WAL-capable SQLite storage, support DATABASE_PATH alias, and document compose secrets via .env.docker.example.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-05 08:21:51 +02:00
co-authored by Cursor
parent e42386a5ee
commit 1261a9b482
7 changed files with 105 additions and 4 deletions
+16
View File
@@ -0,0 +1,16 @@
node_modules
dist
data
docker-data
backups
.env
.env.docker
.git
.gitignore
*.db
*.db-wal
*.db-shm
*.tsbuildinfo
test
coverage
.cursor-sqlite-audit.cjs
+5
View File
@@ -0,0 +1,5 @@
# Copy to .env.docker for docker compose (gitignored).
# Do not commit real secrets.
JWT_SECRET=replace-with-a-strong-secret
CORS_ORIGIN=http://localhost:5174
+3 -2
View File
@@ -6,8 +6,9 @@ NODE_ENV=development
# CORS (Vue dev server)
CORS_ORIGIN=http://localhost:5173
# SQLite — production database
# DB_PATH=E:/absolute/path/to/backend-nest/data/app.db
# SQLite — path inside container or on host (DB_PATH or DATABASE_PATH)
# DB_PATH=/app/data/app.db
# DATABASE_PATH=/app/data/app.db
# JWT — must match legacy Fastify when migrating existing tokens
JWT_SECRET=gold-land-dev-secret-change-in-production
+53
View File
@@ -0,0 +1,53 @@
# syntax=docker/dockerfile:1
FROM node:22.12.0-bookworm-slim AS builder
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci
COPY nest-cli.json tsconfig.json tsconfig.build.json ./
COPY src ./src
RUN npm run build
FROM node:22.12.0-bookworm-slim AS production
WORKDIR /app
ENV NODE_ENV=production \
PORT=3000 \
HOST=0.0.0.0 \
DB_PATH=/app/data/app.db
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json ./
RUN npm ci --omit=dev \
&& npm rebuild better-sqlite3 \
&& apt-get purge -y python3 make g++ \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/dist ./dist
RUN mkdir -p /app/data \
&& chown -R node:node /app
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:3000/health').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "dist/main.js"]
+18
View File
@@ -0,0 +1,18 @@
services:
backend:
build:
context: .
dockerfile: Dockerfile
container_name: child-gold-land-backend
ports:
- "3000:3000"
env_file:
- .env.docker
environment:
NODE_ENV: production
PORT: 3000
HOST: 0.0.0.0
DB_PATH: /app/data/app.db
volumes:
- ./docker-data:/app/data
restart: unless-stopped
+6 -1
View File
@@ -19,6 +19,11 @@ export class EnvironmentVariables {
@IsString()
DB_PATH?: string
/** Alias for DB_PATH (e.g. Docker Compose). DB_PATH takes precedence when both are set. */
@IsOptional()
@IsString()
DATABASE_PATH?: string
@IsOptional()
@IsString()
JWT_SECRET?: string
@@ -43,7 +48,7 @@ export function validateEnv(config: Record<string, unknown>): EnvironmentVariabl
PORT: Number(config.PORT ?? 3001),
HOST: (config.HOST as string | undefined) ?? '0.0.0.0',
CORS_ORIGIN: (config.CORS_ORIGIN as string | undefined) ?? 'http://localhost:5173',
DB_PATH: config.DB_PATH as string | undefined,
DB_PATH: (config.DB_PATH ?? config.DATABASE_PATH) as string | undefined,
JWT_SECRET: jwtSecret ?? 'gold-land-dev-secret-change-in-production',
}
+4 -1
View File
@@ -12,7 +12,10 @@ export class DatabaseService implements OnModuleInit, OnModuleDestroy {
private readonly dbPath: string
constructor(@Optional() @Inject(ConfigService) private readonly config?: ConfigService) {
const configured = this.config?.get<string>('DB_PATH') ?? process.env.DB_PATH
const configured =
this.config?.get<string>('DB_PATH') ??
process.env.DB_PATH ??
process.env.DATABASE_PATH
this.dbPath = configured ?? join(process.cwd(), 'data', 'app.db')
}