# Build-time Environment Variables Configuration Guide ## Overview This guide documents the configuration for build-time environment variables in the Next.js CI/CD pipeline. **Problem Solved:** Next.js 16 requires `NEXT_PUBLIC_*` variables available during `npm run build` for: - SEO metadata (`metadataBase`) - Sitemap generation - OpenGraph URLs - RSS feed URLs **Solution:** Create `.env` file in CI/CD from Gitea secrets, copy to Docker build context, embed variables in JavaScript bundle. --- ## Files Modified ### 1. `.gitea/workflows/main.yml` **Changes:** - Added step to create `.env` from Gitea secrets (after checkout) - Added cleanup step to remove `.env` after Docker push **New Steps:** ```yaml - name: 📝 Create .env file from Gitea secrets run: | echo "Creating .env file for Docker build..." cat > .env << EOF # Build-time environment variables NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }} NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 EOF echo "✅ .env file created successfully" echo "Preview (secrets masked):" cat .env | sed 's/=.*/=***MASKED***/g' ``` ```yaml - name: 🚀 Push Docker image to registry run: | docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest # Clean up sensitive files rm -f .env echo "✅ Cleaned up .env file" ``` --- ### 2. `Dockerfile.nextjs` **Changes:** - Added `COPY .env* ./` in builder stage (after copying node_modules, before copying source code) **Added Section:** ```dockerfile # Copy .env file for build-time variables # This file is created by CI/CD workflow from Gitea secrets # NEXT_PUBLIC_* variables are embedded in client-side bundle during build COPY .env* ./ ``` **Position:** Between `COPY --from=deps /app/node_modules ./node_modules` and `COPY . .` --- ### 3. `.dockerignore` **Changes:** - Modified to allow `.env` file (created by CI/CD) while excluding other `.env*` files **Updated Section:** ``` # Environment files .env* # Exclude all .env files !.env # EXCEPT .env (needed for build from CI/CD) !.env.example # Keep example ``` **Explanation:** - `.env*` excludes all environment files - `!.env` creates exception for main `.env` (from CI/CD) - `.env.local`, `.env.development`, `.env.production.local` remain excluded --- ## Gitea Repository Configuration ### Required Secrets Navigate to: **Repository Settings → Secrets** Add the following secret: | Secret Name | Value | Type | Description | | ---------------------- | ------------------------ | ------------------ | ------------------- | | `NEXT_PUBLIC_SITE_URL` | `https://yourdomain.com` | Secret or Variable | Production site URL | **Notes:** - Can be configured as **Secret** (masked in logs) or **Variable** (visible in logs) - Recommended: Use **Variable** since it's a public URL - For sensitive values (API keys), always use **Secret** ### Adding Additional Variables To add more build-time variables: 1. **Add to Gitea Secrets/Variables:** ``` NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX NEXT_PUBLIC_API_URL=https://api.example.com ``` 2. **Update workflow `.env` creation step:** ```yaml cat > .env << EOF NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }} NEXT_PUBLIC_GA_ID=${{ secrets.NEXT_PUBLIC_GA_ID }} NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }} NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 EOF ``` 3. **No changes needed to Dockerfile or .dockerignore** --- ## Testing ### Local Testing 1. **Create test `.env` file:** ```bash cat > .env << EOF NEXT_PUBLIC_SITE_URL=http://localhost:3030 NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 EOF ``` 2. **Build Docker image:** ```bash docker build -t mypage:test -f Dockerfile.nextjs . ``` 3. **Verify variable is embedded (should show "NOT FOUND" - correct behavior):** ```bash docker run --rm mypage:test node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL || 'NOT FOUND')" ``` **Expected Output:** `NOT FOUND` **Why?** `NEXT_PUBLIC_*` variables are embedded in JavaScript bundle during build, NOT available as runtime environment variables. 4. **Test application starts:** ```bash docker run --rm -p 3030:3030 mypage:test ``` Visit `http://localhost:3030` to verify. 5. **Cleanup:** ```bash rm .env docker rmi mypage:test ``` --- ## CI/CD Pipeline Flow ### Build Process 1. **Checkout code** (`actions/checkout@v4`) 2. **Create `.env` file** from Gitea secrets 3. **Build Docker image:** - Stage 1: Install dependencies - Stage 2: **Copy `.env` → Build Next.js** (variables embedded in bundle) - Stage 3: Production runtime (no `.env` needed) 4. **Push image** to registry 5. **Cleanup `.env` file** from runner ### Deployment Process - Production server pulls pre-built image - No `.env` file needed on production server - Variables already embedded in JavaScript bundle --- ## Security Best Practices ### ✅ Implemented - `.env` file created only in CI/CD runner (not committed to git) - `.env` cleaned up after Docker push - `.gitignore` excludes `.env` files - `.dockerignore` only allows `.env` created by CI/CD ### ⚠️ Important Notes - **DO NOT commit `.env` files** to git repository - **DO NOT store secrets in `NEXT_PUBLIC_*` variables** (they are exposed to client-side) - **USE Gitea Secrets** for sensitive values (API keys, passwords) - **USE Gitea Variables** for non-sensitive config (URLs, feature flags) ### 🔒 Sensitive Data Guidelines | Type | Use For | Access | | --------------- | -------------------------------------------- | ------------------------------ | | `NEXT_PUBLIC_*` | Client-side config (URLs, feature flags) | Public (embedded in JS bundle) | | `SECRET_*` | Server-side secrets (API keys, DB passwords) | Private (runtime only) | --- ## Troubleshooting ### Issue: Variables not available during build **Symptoms:** - Next.js build errors about missing `NEXT_PUBLIC_SITE_URL` - Metadata/sitemap generation fails **Solution:** - Verify `NEXT_PUBLIC_SITE_URL` secret exists in Gitea - Check workflow logs for `.env` creation step - Ensure `.env` file is created BEFORE Docker build ### Issue: Variables not working in application **Symptoms:** - URLs show as `undefined` or `null` in production **Diagnosis:** ```bash # Check if variable is in bundle (should work): curl https://yourdomain.com | grep -o 'NEXT_PUBLIC_SITE_URL' # Check runtime env (should be empty - correct): docker exec mypage-prod node -e "console.log(process.env.NEXT_PUBLIC_SITE_URL)" ``` **Solution:** - Verify `.env` was copied during Docker build - Check Dockerfile logs for `COPY .env* ./` step - Rebuild with `--no-cache` if needed ### Issue: `.env` file not found during Docker build **Symptoms:** - Docker build warning: `COPY .env* ./` - no files matched **Solution:** - Check `.dockerignore` allows `.env` file - Verify workflow creates `.env` BEFORE Docker build - Check file exists: `ls -la .env` in workflow --- ## Verification Checklist After deploying changes: - [ ] Workflow creates `.env` file (check logs) - [ ] Docker build copies `.env` (check build logs) - [ ] Build succeeds without errors - [ ] Application starts in production - [ ] URLs/metadata display correctly - [ ] `.env` cleaned up after push (security) --- ## Additional Resources - [Next.js Environment Variables](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables) - [Docker Build Context](https://docs.docker.com/build/building/context/) - [Gitea Actions Secrets](https://docs.gitea.com/usage/actions/secrets) --- ## Support For issues or questions: 1. Check workflow logs in Gitea Actions 2. Review Docker build logs 3. Verify Gitea secrets configuration 4. Test locally with sample `.env` **Last Updated:** 2025-11-24