From b55bf45d34ffbc778f6a59ca390d218071912ab4 Mon Sep 17 00:00:00 2001 From: achmad Date: Fri, 29 May 2026 17:12:29 +0700 Subject: [PATCH] feat: add Docker setup with multi-stage build Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/.dockerignore | 4 ++ backend/Dockerfile | 40 +++++++++++++++++++ backend/data/.gitkeep | 0 backend/docker-compose.yml | 13 ++++++ backend/docker-entrypoint.sh | 4 ++ backend/src/app/api/admin/arsenal/route.ts | 2 + backend/src/app/api/admin/battlepass/route.ts | 2 + backend/src/app/api/admin/contracts/route.ts | 2 + backend/src/app/api/admin/logout/route.ts | 2 + backend/src/app/api/admin/players/route.ts | 2 + backend/src/app/api/admin/stats/route.ts | 2 + backend/src/app/api/admin/store/route.ts | 2 + 12 files changed, 75 insertions(+) create mode 100644 backend/.dockerignore create mode 100644 backend/Dockerfile create mode 100644 backend/data/.gitkeep create mode 100644 backend/docker-compose.yml create mode 100755 backend/docker-entrypoint.sh diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..9762fa6 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,4 @@ +node_modules +.next +data +README.md diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..d1fd276 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,40 @@ +FROM node:20-alpine AS base + +# Stage 1: Install deps +FROM base AS deps +RUN apk add --no-cache python3 make g++ gcc +WORKDIR /app +COPY package.json package-lock.json* ./ +RUN npm ci + +# Stage 2: Build +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run build + +# Stage 3: Production runner +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV DB_PATH=/app/data/zombie_invasion.db + +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/docker-entrypoint.sh ./ + +RUN chmod +x docker-entrypoint.sh +RUN mkdir -p /app/data && chown -R nextjs:nodejs /app/data + +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +ENTRYPOINT ["/bin/sh", "docker-entrypoint.sh"] diff --git a/backend/data/.gitkeep b/backend/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml new file mode 100644 index 0000000..0b73cff --- /dev/null +++ b/backend/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + app: + build: . + ports: + - "6100:3000" + volumes: + - ./data:/app/data + environment: + - ADMIN_PASSWORD=admin123 + - NODE_ENV=production + restart: unless-stopped diff --git a/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh new file mode 100755 index 0000000..e471f8d --- /dev/null +++ b/backend/docker-entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e +mkdir -p /app/data +exec node server.js diff --git a/backend/src/app/api/admin/arsenal/route.ts b/backend/src/app/api/admin/arsenal/route.ts index 1e9b82c..5475404 100644 --- a/backend/src/app/api/admin/arsenal/route.ts +++ b/backend/src/app/api/admin/arsenal/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; import { getDb } from '@/lib/db'; +export const dynamic = 'force-dynamic'; + export async function GET() { const db = getDb(); const inventory = db.prepare('SELECT * FROM arsenal_inventory ORDER BY steam_id').all(); diff --git a/backend/src/app/api/admin/battlepass/route.ts b/backend/src/app/api/admin/battlepass/route.ts index 2ac168b..22a2baa 100644 --- a/backend/src/app/api/admin/battlepass/route.ts +++ b/backend/src/app/api/admin/battlepass/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; import { getDb } from '@/lib/db'; +export const dynamic = 'force-dynamic'; + export async function GET() { const db = getDb(); const bps = db.prepare('SELECT * FROM battle_passes ORDER BY updated_at DESC').all(); diff --git a/backend/src/app/api/admin/contracts/route.ts b/backend/src/app/api/admin/contracts/route.ts index d84f40b..87651ec 100644 --- a/backend/src/app/api/admin/contracts/route.ts +++ b/backend/src/app/api/admin/contracts/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; import { getDb } from '@/lib/db'; +export const dynamic = 'force-dynamic'; + export async function GET() { const db = getDb(); const contracts = db.prepare('SELECT * FROM death_sentence_contracts').all(); diff --git a/backend/src/app/api/admin/logout/route.ts b/backend/src/app/api/admin/logout/route.ts index bf49cb2..a7d3ac4 100644 --- a/backend/src/app/api/admin/logout/route.ts +++ b/backend/src/app/api/admin/logout/route.ts @@ -1,5 +1,7 @@ import { NextResponse } from 'next/server'; +export const dynamic = 'force-dynamic'; + export async function GET() { const response = NextResponse.json({ success: true }); response.cookies.delete('admin_session'); diff --git a/backend/src/app/api/admin/players/route.ts b/backend/src/app/api/admin/players/route.ts index 0a1c880..efab05a 100644 --- a/backend/src/app/api/admin/players/route.ts +++ b/backend/src/app/api/admin/players/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; import { getDb } from '@/lib/db'; +export const dynamic = 'force-dynamic'; + export async function GET() { const db = getDb(); const players = db.prepare('SELECT * FROM players ORDER BY updated_at DESC LIMIT 100').all(); diff --git a/backend/src/app/api/admin/stats/route.ts b/backend/src/app/api/admin/stats/route.ts index f5dfc8b..0e9ee9f 100644 --- a/backend/src/app/api/admin/stats/route.ts +++ b/backend/src/app/api/admin/stats/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; import { getDb } from '@/lib/db'; +export const dynamic = 'force-dynamic'; + export async function GET() { const db = getDb(); const players = (db.prepare('SELECT COUNT(*) as c FROM players').get() as any).c; diff --git a/backend/src/app/api/admin/store/route.ts b/backend/src/app/api/admin/store/route.ts index 84f2a3c..b19e1ea 100644 --- a/backend/src/app/api/admin/store/route.ts +++ b/backend/src/app/api/admin/store/route.ts @@ -1,6 +1,8 @@ import { NextResponse } from 'next/server'; import { getDb } from '@/lib/db'; +export const dynamic = 'force-dynamic'; + export async function GET() { const db = getDb(); const purchases = db.prepare(`