From 5d71e60e7d809ef57d1d7bf7982d4d83250e2a7b Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Jul 2026 00:19:31 +0200 Subject: [PATCH] feat: Docker Compose support --- .dockerignore | 15 ++++++++++++ Dockerfile | 43 +++++++++++++++++++++++++++++++++ artifacts/api-server/src/app.ts | 11 +++++++++ docker-compose.yml | 32 ++++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..34322fa --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +node_modules +dist +.local +.local-packages +.env +*.md +.gitignore +artifacts/toolrate/node_modules +artifacts/api-server/node_modules +lib/db/node_modules +lib/api-spec/node_modules +lib/api-zod/node_modules +lib/api-client-react/node_modules +scripts/node_modules diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2cb4de2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,43 @@ +FROM node:24-alpine AS builder +WORKDIR /app + +RUN npm install -g pnpm@10.26.1 + +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY lib/db/package.json lib/db/ +COPY lib/api-spec/package.json lib/api-spec/ +COPY lib/api-zod/package.json lib/api-zod/ +COPY lib/api-client-react/package.json lib/api-client-react/ +COPY artifacts/api-server/package.json artifacts/api-server/ +COPY artifacts/toolrate/package.json artifacts/toolrate/ + +RUN pnpm install --frozen-lockfile + +COPY . . +RUN BASE_PATH=/ pnpm run build + +FROM node:24-alpine AS runner +WORKDIR /app + +RUN npm install -g pnpm@10.26.1 + +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY lib/db/package.json lib/db/ +COPY lib/api-spec/package.json lib/api-spec/ +COPY lib/api-zod/package.json lib/api-zod/ +COPY lib/api-client-react/package.json lib/api-client-react/ +COPY artifacts/api-server/package.json artifacts/api-server/ +COPY artifacts/toolrate/package.json artifacts/toolrate/ + +RUN pnpm install --frozen-lockfile --prod + +COPY --from=builder /app/lib/db/dist lib/db/dist +COPY --from=builder /app/artifacts/api-server/dist artifacts/api-server/dist +COPY --from=builder /app/artifacts/toolrate/dist artifacts/toolrate/dist + +ENV NODE_ENV=production +ENV STATIC_DIR=/app/artifacts/toolrate/dist/public + +EXPOSE 8080 + +CMD ["node", "--enable-source-maps", "artifacts/api-server/dist/index.mjs"] diff --git a/artifacts/api-server/src/app.ts b/artifacts/api-server/src/app.ts index 1fa50eb..682e705 100644 --- a/artifacts/api-server/src/app.ts +++ b/artifacts/api-server/src/app.ts @@ -1,5 +1,7 @@ import express, { type Express } from "express"; import cors from "cors"; +import { existsSync } from "node:fs"; +import { resolve } from "node:path"; import pinoHttp from "pino-http"; import session from "express-session"; import ConnectPgSimple from "connect-pg-simple"; @@ -58,4 +60,13 @@ app.use( app.use("/api", router); +const staticDir = process.env.STATIC_DIR; +if (staticDir && existsSync(staticDir)) { + app.use(express.static(staticDir)); + app.get("*", (_req, res) => { + res.sendFile(resolve(staticDir, "index.html")); + }); + logger.info({ staticDir }, "Serving static files"); +} + export default app; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0bb1f3c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +services: + db: + image: postgres:16-alpine + environment: + POSTGRES_USER: toolrate + POSTGRES_PASSWORD: toolrate + POSTGRES_DB: toolrate + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U toolrate"] + interval: 5s + timeout: 5s + retries: 5 + + app: + build: . + ports: + - "8080:8080" + environment: + PORT: "8080" + DATABASE_URL: postgres://toolrate:toolrate@db:5432/toolrate + SESSION_SECRET: change-this-to-a-random-secret + NODE_ENV: production + LOCAL_ADMIN_USERNAME: admin + LOCAL_ADMIN_PASSWORD: pssw0rd + depends_on: + db: + condition: service_healthy + +volumes: + pgdata: