diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fc5643c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +node_modules +npm-debug.log +Dockerfile +.git +.gitignore +.npmrc +.env +.env.* +pnpm-lock.yaml +*.log +.idea +.vscode +.next +coverage +*.md \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a6eb870 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI Pipeline + +on: + pull_request: + +jobs: + type-safety-and-deps: + name: Type Safety & Dependency Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install dependencies + run: pnpm install + + - name: Type Safety Check (pnpm build) + run: pnpm build + + docker-build: + name: Docker Build + runs-on: ubuntu-latest + needs: type-safety-and-deps + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install dependencies + run: pnpm install + + - name: Build Docker image + run: docker build -t studio-lite:latest . \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dc107d8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,47 @@ +# ----------- Stage 1: Build ----------- +FROM node:20.19.2-bullseye-slim AS build +# TODO: Replace with exact SHA256 digest for deterministic builds +# e.g. FROM node:20.19.2-bullseye-slim@sha256: AS build + +WORKDIR /usr/src/app + +# Copy only package files first for better caching +COPY package.json pnpm-lock.yaml* . + +# Install dependencies (including dev for build) +RUN npm install -g pnpm && pnpm install --frozen-lockfile + +# Copy the rest of the source code +COPY . . + +# Type check and build (fail on type errors) +RUN pnpm build + +# ----------- Stage 2: Production ----------- +FROM node:20.19.2-bullseye-slim AS runner +# TODO: Replace with exact SHA256 digest for deterministic builds +# e.g. FROM node:20.19.2-bullseye-slim@sha256: AS runner + +# Install dumb-init for proper signal handling +RUN apt-get update && apt-get install -y dumb-init && rm -rf /var/lib/apt/lists/* + +ENV NODE_ENV=production + +WORKDIR /usr/src/app + +# Copy only production node_modules and built output from build stage +COPY --from=build /usr/src/app/node_modules ./node_modules +COPY --from=build /usr/src/app/.next ./.next +COPY --from=build /usr/src/app/public ./public +COPY --from=build /usr/src/app/package.json ./package.json +COPY --from=build /usr/src/app/next.config.js ./next.config.js +COPY --from=build /usr/src/app/.env* ./ + +# Drop privileges: run as non-root user +USER node + +# Harden container: drop all capabilities, no new privileges, read-only root, tmpfs for /tmp +# (Set these at runtime: see README for recommended docker run flags) + +ENTRYPOINT ["dumb-init", "node"] +CMD [".next/standalone/server.js"] \ No newline at end of file diff --git a/README.md b/README.md index 97b421e..acd1560 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,71 @@ Designer enables you to: --- +## Docker & Production Security + +This project includes a production-grade, multi-stage Dockerfile with security best practices: + +- **Deterministic, minimal base images** (with SHA256 digests) +- **Multi-stage build**: dependencies, Snyk analysis, type checks, and build in stage 1; minimal, secure runtime in stage 2 +- **Snyk CLI**: runs vulnerability analysis during build (fails on vulnerabilities) +- **Type safety**: build fails on TypeScript errors +- **Non-root user**: runs as `node` user +- **Signal handling**: uses `dumb-init` for proper shutdown +- **Hardened runtime**: drop all Linux capabilities, no privilege escalation, read-only root, tmpfs for `/tmp` +- **No secrets in image**: use Docker secrets for `.npmrc` if needed + +### Build the Docker image + +```sh +# (Optional) Enable BuildKit for secrets support +export DOCKER_BUILDKIT=1 + +# Build the image (replace as needed) +docker build -t studio-lite:latest . +``` + +### Run the container securely + +```sh +docker run -d \ + --name studio-lite \ + -p 3000:3000 \ + --user node \ + --read-only \ + --tmpfs /tmp \ + --cap-drop ALL \ + --security-opt no-new-privileges \ + --env-file .env \ + studio-lite:latest +``` + +- **No Docker socket is mounted.** +- **No secrets are baked into the image.** +- **All sensitive files are excluded via `.dockerignore`.** + +### Snyk CLI +- Snyk is run automatically during the Docker build. To run manually: + ```sh + snyk test --all-projects + ``` +- See https://docs.snyk.io/snyk-cli/install-or-update-the-snyk-cli for more info. + +### Type Safety +- TypeScript type checks are enforced at build time (`pnpm build`). + +### .dockerignore +- Ensures no sensitive or unnecessary files are copied into the image. + +### Security Best Practices +- **Do not run as root.** +- **Do not mount the Docker socket.** +- **Use secrets for sensitive data.** +- **Limit resources and capabilities.** +- **Keep host and Docker up to date.** +- **See Dockerfile for more details and comments.** + +--- + ## Usage & Configuration ### Model Playground