From 84aa8d809330e5d2a971539c3506328be86d7893 Mon Sep 17 00:00:00 2001 From: ASOwnerYT Date: Fri, 20 Mar 2026 23:36:24 +1300 Subject: [PATCH] Docker support v2 --- .dockerignore | 134 +++++++++++++++++++++++++++ .github/workflows/docker-build.yml | 64 ------------- .github/workflows/docker-publish.yml | 99 ++++++++++++++++++++ Dockerfile | 128 ++++++++++++++++++------- next.config.mjs | 7 +- 5 files changed, 331 insertions(+), 101 deletions(-) create mode 100644 .dockerignore delete mode 100644 .github/workflows/docker-build.yml create mode 100644 .github/workflows/docker-publish.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c847070 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,134 @@ +############################################################ +# Production-ready .dockerignore for a Next.js (Vercel-style) app +# Keeps Docker builds fast, lean, and free of development files. +############################################################ + +# Dependencies (installed inside Docker, never copied) +node_modules/ +.pnpm-store/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Next.js build outputs (always generated during `next build`) +.next/ +out/ +dist/ +build/ +.vercel/ + +# Tests and testing output (not needed in production images) +coverage/ +.nyc_output/ +__tests__/ +__mocks__/ +jest/ +cypress/ +cypress/screenshots/ +cypress/videos/ +playwright-report/ +test-results/ +.vitest/ +vitest.config.* +jest.config.* +cypress.config.* +playwright.config.* +*.test.* +*.spec.* + +# Local development and editor files +.git/ +.gitignore +.gitattributes +.vscode/ +.idea/ +*.swp +*.swo +*~ +*.log + +# Environment variables (only commit template files) +.env +.env*.local +.env.development +.env.test +.env.production.local + +# Docker configuration files (not needed inside build context) +Dockerfile* +.dockerignore +compose.yaml +compose.yml +docker-compose*.yaml +docker-compose*.yml + +# Documentation +*.md +docs/ + +# CI/CD configuration files +.github/ +.gitlab-ci.yml +.travis.yml +.circleci/ +Jenkinsfile + +# Cache directories and temporary data +.cache/ +.parcel-cache/ +.eslintcache +.stylelintcache +.swc/ +.turbo/ +.tmp/ +.temp/ + +# TypeScript build metadata +*.tsbuildinfo + +# Sensitive or unnecessary configuration files +*.pem +.editorconfig +.prettierrc* +prettier.config.* +.eslintrc* +eslint.config.* +.stylelintrc* +stylelint.config.* +.babelrc* +*.iml +*.ipr +*.iws + +# OS-specific junk +.DS_Store +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +Desktop.ini + +# AI/ML tool metadata and configs +.cursor/ +.cursorrules +.copilot/ +.copilotignore +.github/copilot/ +.gemini/ +.anthropic/ +.kiro +.claude +AGENTS.md +.agents/ + +# AI-generated temp files +*.aider* +*.copilot* +*.chatgpt* +*.claude* +*.gemini* +*.openai* +*.anthropic* \ No newline at end of file diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml deleted file mode 100644 index 8a9b4d2..0000000 --- a/.github/workflows/docker-build.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Build and Push Docker Image - -on: - push: - branches: - - main - - develop - tags: - - 'v*.*.*' - pull_request: - branches: - - main - - develop - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Convert image name to lowercase - id: image - run: echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]' | xargs -I {} echo "name={}" >> $GITHUB_OUTPUT - - - name: Log in to Container Registry - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha - - - name: Build and push Docker image - uses: docker/build-push-action@v5 - with: - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=registry,ref=${{ steps.image.outputs.name }}:buildcache - cache-to: type=registry,ref=${{ steps.image.outputs.name }}:buildcache,mode=max diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..e3a8e22 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,99 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + schedule: + - cron: '38 21 * * *' + push: + branches: [ "master" ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + pull_request: + branches: [ "master" ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 + with: + cosign-release: 'v2.2.4' + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4cf7562..bf4fce4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,112 @@ -# Build stage -FROM node:24-alpine AS builder +# ============================================ +# Stage 1: Dependencies Installation Stage +# ============================================ +# IMPORTANT: Node.js Version Maintenance +# This Dockerfile uses Node.js 24.13.0-slim, which was the latest LTS version at the time of writing. +# To ensure security and compatibility, regularly update the NODE_VERSION ARG to the latest LTS version. +ARG NODE_VERSION=24.13.0-slim + +FROM node:${NODE_VERSION} AS dependencies + +# Set working directory WORKDIR /app -# Install pnpm -RUN npm install -g pnpm +# Copy package-related files first to leverage Docker's caching mechanism +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./ -# Copy package files -COPY pnpm-lock.yaml package.json ./ +# Install project dependencies with frozen lockfile for reproducible builds +RUN --mount=type=cache,target=/root/.npm \ + --mount=type=cache,target=/usr/local/share/.cache/yarn \ + --mount=type=cache,target=/root/.local/share/pnpm/store \ + if [ -f package-lock.json ]; then \ + npm ci --no-audit --no-fund; \ + elif [ -f yarn.lock ]; then \ + corepack enable yarn && yarn install --frozen-lockfile --production=false; \ + elif [ -f pnpm-lock.yaml ]; then \ + corepack enable pnpm && pnpm install --frozen-lockfile; \ + else \ + echo "No lockfile found." && exit 1; \ + fi -# Install dependencies -RUN pnpm install --frozen-lockfile +# ============================================ +# Stage 2: Build Next.js application in standalone mode +# ============================================ -# Copy source code +FROM node:${NODE_VERSION} AS builder + +# Set working directory +WORKDIR /app + +# Copy project dependencies from dependencies stage +COPY --from=dependencies /app/node_modules ./node_modules + +# Copy application source code COPY . . -# Build application -RUN pnpm run build - -# Production stage -FROM node:20-alpine - -WORKDIR /app - -# Install pnpm -RUN npm install -g pnpm - -# Set environment to production ENV NODE_ENV=production -# Copy package files from builder -COPY package.json pnpm-lock.yaml ./ +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED=1 -# Install production dependencies only -RUN pnpm install --prod --frozen-lockfile +# Build Next.js application +# If you want to speed up Docker rebuilds, you can cache the build artifacts +# by adding: --mount=type=cache,target=/app/.next/cache +# This caches the .next/cache directory across builds, but it also prevents +# .next/cache/fetch-cache from being included in the final image, meaning +# cached fetch responses from the build won't be available at runtime. +RUN if [ -f package-lock.json ]; then \ + npm run build; \ + elif [ -f yarn.lock ]; then \ + corepack enable yarn && yarn build; \ + elif [ -f pnpm-lock.yaml ]; then \ + corepack enable pnpm && pnpm build; \ + else \ + echo "No lockfile found." && exit 1; \ + fi -# Copy built application from builder stage -COPY --from=builder /app/.next ./.next -COPY --from=builder /app/public ./public +# ============================================ +# Stage 3: Run Next.js application +# ============================================ -# Expose port +FROM node:${NODE_VERSION} AS runner + +# Set working directory +WORKDIR /app + +# Set production environment variables +ENV NODE_ENV=production +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the run time. +# ENV NEXT_TELEMETRY_DISABLED=1 + +# Copy production assets +COPY --from=builder --chown=node:node /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown node:node .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=node:node /app/.next/standalone ./ +COPY --from=builder --chown=node:node /app/.next/static ./.next/static + +# If you want to persist the fetch cache generated during the build so that +# cached responses are available immediately on startup, uncomment this line: +# COPY --from=builder --chown=node:node /app/.next/cache ./.next/cache + +# Switch to non-root user for security best practices +USER node + +# Expose port 3000 to allow HTTP traffic EXPOSE 3000 -# Start application -CMD ["pnpm", "start"] +# Start Next.js standalone server +CMD ["node", "server.js"] \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 4cd9948..3d55197 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,11 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - typescript: { - ignoreBuildErrors: true, - }, - images: { - unoptimized: true, - }, + output: "standalone", } export default nextConfig