diff --git a/.dockerignore b/.dockerignore index cfeac44d..89f8ec9d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,10 +1,15 @@ .git/ .gitignore -.next/ # Existing Next.js builds .dockerignore Dockerfile docker-compose.yml -node_modules/ # Installed inside container LICENSE README.md -AUTHORS \ No newline at end of file +AUTHORS + +# Reinstalled inside the image (alpine-native — avoids a glibc/musl mismatch) +node_modules/ + +# The built .next output is copied in (built on the CI runner, see +# build_n_push.yml); only the build cache is excluded — it is not served. +.next/cache/ diff --git a/.github/workflows/build_n_push.yml b/.github/workflows/build_n_push.yml index 9e8091e9..3f5f5cdb 100644 --- a/.github/workflows/build_n_push.yml +++ b/.github/workflows/build_n_push.yml @@ -3,31 +3,66 @@ on: push: branches: - main + # Build-only (no push) on PRs that touch the build/deploy pipeline, so + # Dockerfile / workflow changes are validated before they reach main. + pull_request: + paths: + - 'docker/**' + - '.github/workflows/build_n_push.yml' + - 'next.config.mjs' + - 'package-lock.json' workflow_dispatch: - + jobs: docs_build_n_push: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Docker meta + - uses: actions/checkout@v4 + with: + # Full history so gen:last-updated / gen:sitemap can read real + # per-file commit dates (a shallow clone would yield wrong dates). + fetch-depth: 0 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Restore Next.js build cache + uses: actions/cache@v4 + with: + path: .next/cache + key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}-${{ hashFiles('src/**', 'mdx/**', 'next.config.mjs') }} + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }}- + + - name: Build + run: npm run build + + - name: Docker meta id: meta - uses: docker/metadata-action@v3 + uses: docker/metadata-action@v5 with: images: netbirdio/docs.netbird.io - - - name: Login to DockerHub + + - name: Login to DockerHub if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_TOKEN }} - - - name: Docker build and push - uses: docker/build-push-action@v2 + + - name: Docker build and push + uses: docker/build-push-action@v6 with: + context: . file: docker/Dockerfile push: ${{ github.event_name != 'pull_request' }} + # Keep a single-arch manifest (no attestation index) so the server's + # `docker compose pull` stays happy. + provenance: false tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/docker/Dockerfile b/docker/Dockerfile index 0deb1ea3..f810d967 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,17 +10,16 @@ RUN npm install --global pm2 # Utilise Docker cache to save re-installing dependencies if unchanged COPY ./package*.json ./ -# Install dependencies (npm ci = clean, reproducible install from the lockfile) +# Install runtime dependencies (npm ci = clean, reproducible install from the lockfile) RUN npm ci --omit=dev -# Copy all files +# Copy the app, including the prebuilt .next output. The build now runs on the +# CI runner (see .github/workflows/build_n_push.yml) with a warm .next/cache, +# so there is no `npm run build` step here — we only package the result. COPY ./ ./ COPY /docker/entrypoint.sh ./entrypoint.sh -# Build app -RUN npm run build - RUN chmod u+x /usr/app/entrypoint.sh # Expose the listening port diff --git a/scripts/git-dates.mjs b/scripts/git-dates.mjs index 4912d390..cacd3506 100644 --- a/scripts/git-dates.mjs +++ b/scripts/git-dates.mjs @@ -32,13 +32,24 @@ let _dateMapCache * repo-relative with forward slashes, matching path.relative(repoRoot, file). * * Returns an empty map if git is unavailable (e.g. inside the Docker image, - * which has no git binary); callers then fall back to no date, exactly as the - * per-file getGitLastModified() did. + * which has no git binary) or the checkout is shallow; callers then fall back + * to no date, exactly as the per-file getGitLastModified() did. */ export function buildGitDateMap() { if (_dateMapCache) return _dateMapCache const map = new Map() try { + // On a shallow clone (e.g. actions/checkout without fetch-depth: 0) git log + // only sees the fetched commits, so every file would report the same wrong + // date. Emit no dates rather than wrong ones. + const shallow = execSync('git rev-parse --is-shallow-repository', { + encoding: 'utf-8', + stdio: ['pipe', 'pipe', 'ignore'], + }).trim() + if (shallow === 'true') { + _dateMapCache = map + return map + } // core.quotePath=false keeps non-ASCII paths unquoted so line parsing is safe. const out = execSync( 'git -c core.quotePath=false log --format=%cI --name-only',