chore(devops): add Dockerfiles, docker-compose, CI workflow, docker-prep script [C1-C5]

- backend/Dockerfile: multi-stage Node.js build (install → build → runtime)
- web/Dockerfile: multi-stage Next.js standalone build
- docker-compose.yml: backend (4016) + web (3000) with health check
- scripts/docker-prep.sh: pack @bytelyst/* tarballs + rewrite file: refs (--restore to undo)
- .github/workflows/ci.yml: backend (typecheck+test+build), web (typecheck+test+build), mobile (typecheck)
This commit is contained in:
saravanakumardb1 2026-03-19 08:47:04 -07:00
parent a3267e4b1b
commit a71747e3fb
5 changed files with 246 additions and 0 deletions

64
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,64 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
backend:
name: Backend
runs-on: ubuntu-latest
defaults:
run:
working-directory: backend
env:
NODE_ENV: test
JWT_SECRET: ci-test-secret
DB_PROVIDER: memory
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: backend/package-lock.json
- run: npm ci
- run: npm run typecheck
- run: npm test
- run: npm run build
web:
name: Web
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: web/package-lock.json
- run: npm ci
- run: npm run typecheck
- run: npm test
- run: npm run build
mobile:
name: Mobile Typecheck
runs-on: ubuntu-latest
defaults:
run:
working-directory: mobile
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: mobile/package-lock.json
- run: npm ci
- run: npm run typecheck

25
backend/Dockerfile Normal file
View File

@ -0,0 +1,25 @@
# ── Stage 1: Install ──────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production=false
# ── Stage 2: Build ────────────────────────────────────
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# ── Stage 3: Runtime ──────────────────────────────────
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 4016
CMD ["node", "dist/server.js"]

42
docker-compose.yml Normal file
View File

@ -0,0 +1,42 @@
version: "3.9"
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "4016:4016"
environment:
- NODE_ENV=production
- PORT=4016
- HOST=0.0.0.0
- JWT_SECRET=${JWT_SECRET:-dev-secret-change-me}
- COSMOS_ENDPOINT=${COSMOS_ENDPOINT:-}
- COSMOS_KEY=${COSMOS_KEY:-}
- COSMOS_DATABASE=${COSMOS_DATABASE:-bytelyst}
- DB_PROVIDER=${DB_PROVIDER:-memory}
- CORS_ORIGIN=${CORS_ORIGIN:-http://localhost:3000}
- PLATFORM_SERVICE_URL=${PLATFORM_SERVICE_URL:-http://localhost:4003}
- EXTRACTION_SERVICE_URL=${EXTRACTION_SERVICE_URL:-http://localhost:4005}
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:4016/health"]
interval: 30s
timeout: 5s
retries: 3
web:
build:
context: ./web
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_BACKEND_URL=http://backend:4016
- NEXT_PUBLIC_PLATFORM_URL=${PLATFORM_SERVICE_URL:-http://localhost:4003}
depends_on:
backend:
condition: service_healthy
restart: unless-stopped

85
scripts/docker-prep.sh Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -euo pipefail
# docker-prep.sh — Pack @bytelyst/* packages into tarballs for Docker builds
# Usage:
# ./scripts/docker-prep.sh # pack tarballs + rewrite package.json
# ./scripts/docker-prep.sh --restore # undo changes
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
COMMON_PLAT="${ROOT_DIR}/../learning_ai_common_plat"
TARBALL_DIR="${ROOT_DIR}/.tarballs"
if [[ "${1:-}" == "--restore" ]]; then
echo "Restoring package.json files..."
for pkg_json in backend/package.json web/package.json; do
backup="${ROOT_DIR}/${pkg_json}.bak"
if [[ -f "$backup" ]]; then
mv "$backup" "${ROOT_DIR}/${pkg_json}"
echo " Restored ${pkg_json}"
fi
done
rm -rf "$TARBALL_DIR"
echo "Done."
exit 0
fi
if [[ ! -d "$COMMON_PLAT/packages" ]]; then
echo "ERROR: Cannot find $COMMON_PLAT/packages — run from the correct directory"
exit 1
fi
echo "Building @bytelyst/* packages..."
(cd "$COMMON_PLAT" && pnpm build)
echo "Packing tarballs..."
rm -rf "$TARBALL_DIR"
mkdir -p "$TARBALL_DIR"
for pkg_dir in "$COMMON_PLAT"/packages/*/; do
pkg_name=$(basename "$pkg_dir")
if [[ -f "$pkg_dir/package.json" ]]; then
(cd "$pkg_dir" && npm pack --pack-destination "$TARBALL_DIR" 2>/dev/null) || true
fi
done
echo "Tarballs created:"
ls -la "$TARBALL_DIR"/*.tgz 2>/dev/null || echo " (none)"
echo "Rewriting package.json file: refs..."
for pkg_json in backend/package.json web/package.json; do
full_path="${ROOT_DIR}/${pkg_json}"
if [[ ! -f "$full_path" ]]; then
continue
fi
cp "$full_path" "${full_path}.bak"
# Replace file:../../learning_ai_common_plat/packages/* refs with tarball paths
node -e "
const fs = require('fs');
const path = require('path');
const pkg = JSON.parse(fs.readFileSync('$full_path', 'utf8'));
const tarballDir = '$TARBALL_DIR';
const tarballs = fs.readdirSync(tarballDir).filter(f => f.endsWith('.tgz'));
for (const depType of ['dependencies', 'devDependencies']) {
if (!pkg[depType]) continue;
for (const [name, version] of Object.entries(pkg[depType])) {
if (typeof version === 'string' && version.startsWith('file:')) {
const shortName = name.replace('@bytelyst/', 'bytelyst-');
const tarball = tarballs.find(t => t.startsWith(shortName));
if (tarball) {
pkg[depType][name] = 'file:' + path.join(tarballDir, tarball);
}
}
}
}
fs.writeFileSync('$full_path', JSON.stringify(pkg, null, 2) + '\n');
"
echo " Rewrote ${pkg_json}"
done
echo "Done. Run 'docker build' now. Use --restore to undo."

30
web/Dockerfile Normal file
View File

@ -0,0 +1,30 @@
# ── Stage 1: Install ──────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# ── Stage 2: Build ────────────────────────────────────
FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Dummy env vars for Next.js build-time page data collection
ENV NEXT_PUBLIC_BACKEND_URL=http://localhost:4016
ENV NEXT_PUBLIC_PLATFORM_URL=http://localhost:4003
RUN npm run build
# ── Stage 3: Runtime ──────────────────────────────────
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]