feat: Dockerfiles and compose deploy setup
This commit is contained in:
34
.env.example
Normal file
34
.env.example
Normal file
@@ -0,0 +1,34 @@
|
||||
# ── Required ──────────────────────────────────────────────────────────────────
|
||||
|
||||
# 64 hex characters (32 random bytes). Generate with:
|
||||
# openssl rand -hex 32
|
||||
GATEWAY_SECURITY_ENCRYPTION_KEY=
|
||||
|
||||
# Canonical public URL (used to build OIDC redirect_uri)
|
||||
GATEWAY_SERVER_PUBLIC_URL=https://lmr.example.com
|
||||
|
||||
GATEWAY_OIDC_ISSUER=https://your-oidc-provider.example.com
|
||||
GATEWAY_OIDC_CLIENT_ID=
|
||||
GATEWAY_OIDC_CLIENT_SECRET=
|
||||
GATEWAY_OIDC_ADMIN_GROUP=
|
||||
|
||||
# ── Database ──────────────────────────────────────────────────────────────────
|
||||
# compose.yml (bundled): postgres runs inside the container — leave this as-is.
|
||||
# compose.standard.yml: set host to the postgres service name, e.g.:
|
||||
# GATEWAY_DB_URL=postgres://lmr:lmr@postgres:5432/lmr?sslmode=disable
|
||||
GATEWAY_DB_URL=postgres://lmr:lmr@localhost:5432/lmr?sslmode=disable
|
||||
|
||||
# ── Optional ──────────────────────────────────────────────────────────────────
|
||||
# GATEWAY_SERVER_PORT=8080
|
||||
# GATEWAY_SERVER_ENV=production
|
||||
# GATEWAY_DB_MAX_CONNS=20
|
||||
# GATEWAY_DB_MIN_CONNS=2
|
||||
# GATEWAY_CATALOG_SYNC_INTERVAL=30m
|
||||
|
||||
# Host port to expose (maps to container :8080)
|
||||
# PORT=8080
|
||||
|
||||
# Postgres credentials (bundled and compose.standard.yml postgres service)
|
||||
# PGUSER=lmr
|
||||
# PGPASSWORD=lmr
|
||||
# PGDATABASE=lmr
|
||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# ── Build stages run on the build host (amd64); Go cross-compiles for TARGETARCH.
|
||||
# This avoids QEMU emulation and cuts arm64 build time from ~15min to ~2min.
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:22-alpine AS ui
|
||||
WORKDIR /src/dashboard
|
||||
COPY backend/dashboard/package*.json ./
|
||||
RUN npm ci
|
||||
COPY backend/dashboard/ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS go-build
|
||||
ARG TARGETARCH
|
||||
ENV GOTOOLCHAIN=auto CGO_ENABLED=0 GOARCH=$TARGETARCH
|
||||
WORKDIR /src
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
COPY backend/ .
|
||||
COPY --from=ui /src/dashboard/../internal/web/dist ./internal/web/dist
|
||||
RUN go build -trimpath -ldflags="-s -w" -o /lmr ./cmd/server
|
||||
|
||||
# ── Runtime stage ─────────────────────────────────────────────────────────────
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
COPY --from=go-build /lmr /lmr
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/lmr"]
|
||||
40
Dockerfile.bundled
Normal file
40
Dockerfile.bundled
Normal file
@@ -0,0 +1,40 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# ── Build stages run on the build host (amd64); Go cross-compiles for TARGETARCH.
|
||||
|
||||
FROM --platform=$BUILDPLATFORM node:22-alpine AS ui
|
||||
WORKDIR /src/dashboard
|
||||
COPY backend/dashboard/package*.json ./
|
||||
RUN npm ci
|
||||
COPY backend/dashboard/ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS go-build
|
||||
ARG TARGETARCH
|
||||
ENV GOTOOLCHAIN=auto CGO_ENABLED=0 GOARCH=$TARGETARCH
|
||||
WORKDIR /src
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
COPY backend/ .
|
||||
COPY --from=ui /src/dashboard/../internal/web/dist ./internal/web/dist
|
||||
RUN go build -trimpath -ldflags="-s -w" -o /lmr ./cmd/server
|
||||
|
||||
# ── Runtime stage (Postgres bundled) ─────────────────────────────────────────
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache postgresql16 postgresql16-client su-exec bash
|
||||
|
||||
COPY --from=go-build /lmr /usr/local/bin/lmr
|
||||
COPY deploy/entrypoint-bundled.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
ENV PGDATA=/var/lib/postgresql/data
|
||||
ENV PGUSER=lmr
|
||||
ENV PGPASSWORD=lmr
|
||||
ENV PGDATABASE=lmr
|
||||
# The gateway reads GATEWAY_DB_URL; set a default that points to the local PG.
|
||||
ENV GATEWAY_DB_URL="postgres://lmr:lmr@localhost:5432/lmr?sslmode=disable"
|
||||
|
||||
VOLUME ["/var/lib/postgresql/data"]
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
30
compose.standard.yml
Normal file
30
compose.standard.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: ${PGUSER:-lmr}
|
||||
POSTGRES_PASSWORD: ${PGPASSWORD:-lmr}
|
||||
POSTGRES_DB: ${PGDATABASE:-lmr}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${PGUSER:-lmr} -d ${PGDATABASE:-lmr}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
lmr:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "${PORT:-8080}:8080"
|
||||
env_file: .env
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
14
compose.yml
Normal file
14
compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
lmr:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.bundled
|
||||
ports:
|
||||
- "${PORT:-8080}:8080"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
env_file: .env
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
44
deploy/entrypoint-bundled.sh
Normal file
44
deploy/entrypoint-bundled.sh
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PGDATA="${PGDATA:-/var/lib/postgresql/data}"
|
||||
PGUSER="${PGUSER:-lmr}"
|
||||
PGPASSWORD="${PGPASSWORD:-lmr}"
|
||||
PGDATABASE="${PGDATABASE:-lmr}"
|
||||
|
||||
# ── Initialise cluster if needed ─────────────────────────────────────────────
|
||||
if [ ! -f "$PGDATA/PG_VERSION" ]; then
|
||||
echo "→ initialising postgres cluster"
|
||||
install -d -m 0700 -o postgres "$PGDATA"
|
||||
su-exec postgres initdb \
|
||||
--username="$PGUSER" \
|
||||
--pwfile=<(echo "$PGPASSWORD") \
|
||||
--auth=md5 \
|
||||
-D "$PGDATA" \
|
||||
--no-instructions
|
||||
fi
|
||||
|
||||
# ── Start postgres in the background ─────────────────────────────────────────
|
||||
echo "→ starting postgres"
|
||||
su-exec postgres postgres -D "$PGDATA" \
|
||||
-c listen_addresses=localhost \
|
||||
-c log_min_messages=WARNING &
|
||||
PG_PID=$!
|
||||
|
||||
# ── Wait for postgres to accept connections ───────────────────────────────────
|
||||
echo "→ waiting for postgres"
|
||||
until su-exec postgres pg_isready -U "$PGUSER" -d "$PGDATABASE" -q 2>/dev/null; do
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
# ── Ensure the application database exists ───────────────────────────────────
|
||||
su-exec postgres psql -U "$PGUSER" -tc \
|
||||
"SELECT 1 FROM pg_database WHERE datname = '$PGDATABASE'" \
|
||||
| grep -q 1 || su-exec postgres createdb -U "$PGUSER" "$PGDATABASE"
|
||||
|
||||
# ── Start the gateway (foreground, takes over the process) ───────────────────
|
||||
echo "→ starting lmr"
|
||||
# Forward signals to both processes on exit.
|
||||
trap 'kill $PG_PID 2>/dev/null; wait $PG_PID' TERM INT
|
||||
|
||||
exec /usr/local/bin/lmr
|
||||
Reference in New Issue
Block a user