diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a07e792 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +node_modules +.next +.git +.gitignore + +# Local env files — set these as environment variables in Coolify instead +.env +.env.local +.env.*.local + +# SQLite database files — mount /app/prisma as a persistent volume in Coolify +prisma/*.db +prisma/*.db-shm +prisma/*.db-wal +prisma/migrations + +# Dev/test artifacts +coverage +*.log +vitest.config.ts +src/**/*.test.ts +src/**/*.test.tsx +test/ diff --git a/.env.example b/.env.example index 0f574b1..b379755 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,18 @@ -# Stripe Keys -# Get your keys from https://dashboard.stripe.com/apikeys -NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_51RBtf7I7xOcO9rzigsLLK3esMLmBlJoRztbzUadPhQm7tcHQuScViFEkwdfAwDxbaqt5n8BOuJV9wRSMdn2IrxIX00lqGOOJfT -STRIPE_SECRET_KEY=sk_test_51RBtf7I7xOcO9rzitxMqK3jnTb3SPdEbyGxGBnPccGEfrIrpiEFEOIEG2oHuTumaUejUN4FyAOBg0AVCBRn6AOKI00LeWSDC10 +# ───────────────────────────────────────────────────────────────────────────── +# Stripe — https://dashboard.stripe.com/apikeys +# ───────────────────────────────────────────────────────────────────────────── +NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_... +STRIPE_SECRET_KEY=sk_live_... +STRIPE_WEBHOOK_SECRET=whsec_... + +# ───────────────────────────────────────────────────────────────────────────── +# Admin JWT (generate with: openssl rand -hex 32) +# ───────────────────────────────────────────────────────────────────────────── +ADMIN_JWT_SECRET=change_me_generate_with_openssl_rand_hex_32 + +# ───────────────────────────────────────────────────────────────────────────── +# Database — SQLite via libsql +# In Coolify: set to file:/app/prisma/lootah.db +# and mount /app/prisma as a persistent volume +# ───────────────────────────────────────────────────────────────────────────── +DATABASE_URL=file:./prisma/lootah.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c61dd55 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,62 @@ +# ── Stage 1: install all dependencies ───────────────────────────────────────── +FROM node:22.14-alpine AS deps +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci + +# ── Stage 2: build ───────────────────────────────────────────────────────────── +FROM node:22.14-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ENV NEXT_TELEMETRY_DISABLED=1 + +# Generate Prisma client (reads prisma/schema.prisma + prisma.config.ts) +RUN npx prisma generate + +# Build Next.js — produces .next/standalone (set in next.config.mjs) +RUN npm run build + +# ── Stage 3: production runner ───────────────────────────────────────────────── +FROM node:22.14-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME=0.0.0.0 + +# dumb-init: proper PID 1 / signal forwarding +RUN apk add --no-cache dumb-init \ + && addgroup --system --gid 1001 nodejs \ + && adduser --system --uid 1001 nextjs + +# ── Next.js standalone server ────────────────────────────────────────────────── +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /app/public ./public + +# ── Prisma runtime ───────────────────────────────────────────────────────────── +# Config + schema (read by CLI at container startup for db push) +COPY --from=builder --chown=nextjs:nodejs /app/prisma.config.ts ./ +COPY --from=builder --chown=nextjs:nodejs /app/prisma/schema.prisma ./prisma/ + +# Generated Prisma client (imported by the compiled Next.js server bundle) +COPY --from=builder --chown=nextjs:nodejs /app/src/generated ./src/generated + +# Prisma CLI + its dependencies (devDeps — not bundled into standalone node_modules) +COPY --from=builder --chown=nextjs:nodejs /app/node_modules/.bin/prisma ./node_modules/.bin/prisma +COPY --from=builder --chown=nextjs:nodejs /app/node_modules/prisma ./node_modules/prisma +COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@prisma ./node_modules/@prisma +COPY --from=builder --chown=nextjs:nodejs /app/node_modules/@libsql ./node_modules/@libsql + +# ── Entrypoint ───────────────────────────────────────────────────────────────── +COPY --chown=nextjs:nodejs docker-entrypoint.sh ./ +RUN chmod +x /app/docker-entrypoint.sh + +USER nextjs +EXPOSE 3000 + +# dumb-init wraps our entrypoint so SIGTERM is forwarded to node properly +ENTRYPOINT ["/usr/bin/dumb-init", "--", "/app/docker-entrypoint.sh"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..06cf2a3 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -e + +# Ensure the persistent database directory exists +# (Coolify: mount a volume at /app/prisma so data survives redeployments) +mkdir -p /app/prisma + +echo "→ Syncing database schema..." +# db push creates the SQLite file and syncs tables to match schema.prisma +# --skip-generate: client was already generated at build time +/app/node_modules/.bin/prisma db push --skip-generate + +echo "→ Starting Next.js on port ${PORT:-3000}..." +exec node /app/server.js diff --git a/prisma.config.ts b/prisma.config.ts index 47b92ff..a0924f4 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -6,6 +6,8 @@ export default defineConfig({ path: "prisma/migrations", }, datasource: { - url: "file:./prisma/lootah.db", + // In production (Coolify), set DATABASE_URL=file:/app/prisma/lootah.db + // and mount /app/prisma as a persistent volume. + url: process.env.DATABASE_URL ?? "file:./prisma/lootah.db", }, }); diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 7ee832b..f909ac6 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -5,8 +5,10 @@ import path from 'path'; const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; function createPrismaClient() { - const dbPath = path.resolve(process.cwd(), 'prisma/lootah.db'); - const adapter = new PrismaLibSql({ url: `file:${dbPath}` }); + // Use DATABASE_URL if set (production/Coolify), otherwise fall back to local path. + const dbUrl = process.env.DATABASE_URL + ?? `file:${path.resolve(process.cwd(), 'prisma/lootah.db')}`; + const adapter = new PrismaLibSql({ url: dbUrl }); return new PrismaClient({ adapter } as ConstructorParameters[0]); }