Add Docker stack for self-hosted deployment

Brings full Postgres + api + web compose setup so the app can run on any
machine with just Docker, instead of requiring a host PostgreSQL install.
Includes a one-shot migrate service that pushes the Drizzle schema before
the API starts. Web image serves the Vite build via nginx and proxies
/api/* to the api-server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
sylvain p 2026-05-07 12:11:36 +02:00
parent 9b1ad391ca
commit a891b56360
7 changed files with 160 additions and 0 deletions

11
.dockerignore Normal file
View File

@ -0,0 +1,11 @@
**/node_modules
**/dist
**/.cache
**/.local
**/*.tsbuildinfo
.git
.replit
.replitignore
.vscode
.idea
.DS_Store

4
.env.example Normal file
View File

@ -0,0 +1,4 @@
POSTGRES_USER=cra
POSTGRES_PASSWORD=cra
POSTGRES_DB=cra
WEB_PORT=8080

4
.gitignore vendored
View File

@ -47,3 +47,7 @@ Thumbs.db
# Replit
.cache/
.local/
# Local env
.env

56
docker-compose.yml Normal file
View File

@ -0,0 +1,56 @@
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-cra}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-cra}
POSTGRES_DB: ${POSTGRES_DB:-cra}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-cra} -d ${POSTGRES_DB:-cra}"]
interval: 5s
timeout: 3s
retries: 10
migrate:
build:
context: .
dockerfile: docker/api.Dockerfile
target: migrate
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-cra}:${POSTGRES_PASSWORD:-cra}@postgres:5432/${POSTGRES_DB:-cra}
depends_on:
postgres:
condition: service_healthy
restart: "no"
api:
build:
context: .
dockerfile: docker/api.Dockerfile
target: runtime-api
restart: unless-stopped
environment:
DATABASE_URL: postgres://${POSTGRES_USER:-cra}:${POSTGRES_PASSWORD:-cra}@postgres:5432/${POSTGRES_DB:-cra}
PORT: "3000"
NODE_ENV: production
depends_on:
postgres:
condition: service_healthy
migrate:
condition: service_completed_successfully
web:
build:
context: .
dockerfile: docker/web.Dockerfile
restart: unless-stopped
ports:
- "${WEB_PORT:-8080}:80"
depends_on:
- api
volumes:
postgres_data:

34
docker/api.Dockerfile Normal file
View File

@ -0,0 +1,34 @@
# syntax=docker/dockerfile:1.7
FROM node:24-alpine AS base
RUN npm install -g pnpm@10
WORKDIR /repo
FROM base AS deps
COPY .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json tsconfig.json ./
COPY artifacts/api-server/package.json artifacts/api-server/
COPY artifacts/cra-app/package.json artifacts/cra-app/
COPY artifacts/mockup-sandbox/package.json artifacts/mockup-sandbox/
COPY lib/api-client-react/package.json lib/api-client-react/
COPY lib/api-spec/package.json lib/api-spec/
COPY lib/api-zod/package.json lib/api-zod/
COPY lib/db/package.json lib/db/
COPY scripts/package.json scripts/
RUN pnpm install --frozen-lockfile
FROM deps AS build-api
COPY lib lib
COPY artifacts/api-server artifacts/api-server
RUN pnpm --filter @workspace/api-server run build
FROM node:24-alpine AS runtime-api
WORKDIR /app
COPY --from=build-api /repo/artifacts/api-server/dist ./dist
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "--enable-source-maps", "dist/index.mjs"]
FROM deps AS migrate
COPY lib lib
WORKDIR /repo
CMD ["pnpm", "--filter", "@workspace/db", "run", "push-force"]

20
docker/nginx.conf Normal file
View File

@ -0,0 +1,20 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location /api/ {
proxy_pass http://api:3000/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
try_files $uri $uri/ /index.html;
}
}

31
docker/web.Dockerfile Normal file
View File

@ -0,0 +1,31 @@
# syntax=docker/dockerfile:1.7
FROM node:24-slim AS base
RUN npm install -g pnpm@10
WORKDIR /repo
FROM base AS deps
COPY .npmrc package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json tsconfig.json ./
COPY artifacts/api-server/package.json artifacts/api-server/
COPY artifacts/cra-app/package.json artifacts/cra-app/
COPY artifacts/mockup-sandbox/package.json artifacts/mockup-sandbox/
COPY lib/api-client-react/package.json lib/api-client-react/
COPY lib/api-spec/package.json lib/api-spec/
COPY lib/api-zod/package.json lib/api-zod/
COPY lib/db/package.json lib/db/
COPY scripts/package.json scripts/
RUN pnpm install --frozen-lockfile
FROM deps AS build
COPY lib lib
COPY artifacts/cra-app artifacts/cra-app
COPY attached_assets attached_assets
ENV NODE_ENV=production
ENV PORT=80
ENV BASE_PATH=/
RUN pnpm --filter @workspace/cra-app run build
FROM nginx:alpine AS runtime
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /repo/artifacts/cra-app/dist/public /usr/share/nginx/html
EXPOSE 80