318 lines
7.8 KiB
Markdown
318 lines
7.8 KiB
Markdown
# 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
|