A complete GitLab CI/CD pipeline template for deploying Next.js apps to Google Cloud Run. Supports staging & production environments with custom configs and best practices.
This document provides a comprehensive,GitLab CI/CD pipeline template for deploying Next.js applications to Google Cloud Run. The pipeline supports separate staging and production environments with customizable configurations for different project requirements.
The template is designed to be flexible and easily adaptable to various Next.js projects, allowing developers to add or remove services as needed while maintaining best practices for security, deployment, and environment management.
Run the following commands for both staging and production projects:
# Replace 'your-project-id' with your actual project ID
gcloud config set project your-project-id
# Enable required Google Cloud APIs
gcloud services enable cloudbuild.googleapis.com
gcloud services enable run.googleapis.com
gcloud services enable artifactregistry.googleapis.com
Create Docker repositories in both projects to store your application images:
# For staging project
gcloud artifacts repositories create your-app-staging \
--repository-format=docker \
--location=your-preferred-region \
--project=your-staging-project-id
# For production project
gcloud artifacts repositories create your-app-production \
--repository-format=docker \
--location=your-preferred-region \
--project=your-production-project-id
Create dedicated service accounts for GitLab CI/CD operations:
# Staging service account
gcloud iam service-accounts create gitlab-ci-staging \
--display-name="GitLab CI Staging Deployment" \
--description="Service account for GitLab CI staging deployments" \
--project=your-staging-project-id
# Production service account
gcloud iam service-accounts create gitlab-ci-production \
--display-name="GitLab CI Production Deployment" \
--description="Service account for GitLab CI production deployments" \
--project=your-production-project-id
Assign necessary roles to service accounts for deployment operations:
# Staging permissions
gcloud projects add-iam-policy-binding your-staging-project-id \
--member="serviceAccount:gitlab-ci-staging@your-staging-project-id.iam.gserviceaccount.com" \
--role="roles/run.admin"
gcloud projects add-iam-policy-binding your-staging-project-id \
--member="serviceAccount:gitlab-ci-staging@your-staging-project-id.iam.gserviceaccount.com" \
--role="roles/artifactregistry.writer"
gcloud projects add-iam-policy-binding your-staging-project-id \
--member="serviceAccount:gitlab-ci-staging@your-staging-project-id.iam.gserviceaccount.com" \
--role="roles/iam.serviceAccountUser"
# Production permissions (replace with your production project details)
gcloud projects add-iam-policy-binding your-production-project-id \
--member="serviceAccount:gitlab-ci-production@your-production-project-id.iam.gserviceaccount.com" \
--role="roles/run.admin"
gcloud projects add-iam-policy-binding your-production-project-id \
--member="serviceAccount:gitlab-ci-production@your-production-project-id.iam.gserviceaccount.com" \
--role="roles/artifactregistry.writer"
gcloud projects add-iam-policy-binding your-production-project-id \
--member="serviceAccount:gitlab-ci-production@your-production-project-id.iam.gserviceaccount.com" \
--role="roles/iam.serviceAccountUser"
Generate JSON key files for authentication:
# Staging service account key
gcloud iam service-accounts keys create staging-key.json \
--iam-account=gitlab-ci-staging@your-staging-project-id.iam.gserviceaccount.com
# Production service account key
gcloud iam service-accounts keys create production-key.json \
--iam-account=gitlab-ci-production@your-production-project-id.iam.gserviceaccount.com
| Variable Name | Description | Type |
|---|---|---|
| STAGING_GCP_SERVICE_ACCOUNT | Content of staging-key.json file | File/Masked |
| PROD_GCP_SERVICE_ACCOUNT | Content of production-key.json file | File/Masked |
Add these variables based on your application's requirements. Remove or modify as needed:
| Staging Variable | Production Variable | Description |
|---|---|---|
| STAGING_API_BASE_URL | PROD_API_BASE_URL | Backend API endpoint URL |
| STAGING_WEB_BASE_URL | PROD_WEB_BASE_URL | Frontend application URL |
| STAGING_DATABASE_URL | PROD_DATABASE_URL | Database connection string |
| STAGING_REDIS_URL | PROD_REDIS_URL | Redis cache connection URL |
| STAGING_NEXTAUTH_SECRET | PROD_NEXTAUTH_SECRET | NextAuth.js secret key |
| STAGING_NEXTAUTH_URL | PROD_NEXTAUTH_URL | NextAuth.js canonical URL |
Ensure your Next.js project has the following structure:
your-nextjs-project/
├── .gitlab-ci.yml # GitLab CI/CD configuration
├── Dockerfile # Docker container configuration
├── .dockerignore # Docker ignore patterns
├── package.json # Node.js dependencies
├── next.config.js # Next.js configuration
├── src/ # Source code directory
└── public/ # Static assets
Create a Dockerfile in your project root with the
followingtemplate:
# =================================================================
# Multi-stage Dockerfile for Next.js Application
# =================================================================
# Stage 1: Dependencies installation
FROM node:18-alpine AS dependencies
WORKDIR /app
# Copy package files for dependency installation
COPY package*.json ./
# Install dependencies (production and development)
RUN npm ci
# =================================================================
# Stage 2: Build stage
FROM node:18-alpine AS builder
WORKDIR /app
# Copy dependencies from previous stage
COPY --from=dependencies /app/node_modules ./node_modules
# Copy source code
COPY . .
# Accept build arguments for environment variables
# Add or remove build arguments based on your application needs
ARG API_BASE_URL
ARG WEB_BASE_URL
ARG DATABASE_URL
ARG NEXTAUTH_SECRET
ARG NEXTAUTH_URL
# Set environment variables for build process
ENV API_BASE_URL=$API_BASE_URL
ENV WEB_BASE_URL=$WEB_BASE_URL
ENV DATABASE_URL=$DATABASE_URL
ENV NEXTAUTH_SECRET=$NEXTAUTH_SECRET
ENV NEXTAUTH_URL=$NEXTAUTH_URL
ENV NODE_ENV=production
# Build the Next.js application
RUN npm run build
# =================================================================
# Stage 3: Production runtime
FROM node:18-alpine AS runner
WORKDIR /app
# Create non-root user for security
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy built application from builder stage
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
# Switch to non-root user
USER nextjs
# Expose port (Cloud Run uses PORT environment variable)
EXPOSE 3000
# Set environment variables for runtime
ENV PORT=3000
ENV NODE_ENV=production
# Health check for container monitoring
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/api/health || exit 1
# Start the application
CMD ["node", "server.js"]
Create a .dockerignore file to exclude unnecessary
files:
# Dependencies
node_modules
npm-debug.log*
# Build outputs
.next
out
# Development files
.env*.local
.env.development
.env.test
# IDE and editor files
.vscode
.idea
*.swp
*.swo
# OS generated files
.DS_Store
Thumbs.db
# Git
.git
.gitignore
# Documentation
README.md
docs/
# CI/CD files
.gitlab-ci.yml
.github/
# Test files
__tests__
*.test.js
*.spec.js
coverage/
Create a .gitlab-ci.yml file in your project root with the
followingtemplate:
# =================================================================
#GitLab CI/CD Pipeline for Next.js on Google Cloud Run
# =================================================================
#
# This pipeline provides a flexible template for deploying Next.js applications
# to Google Cloud Run with separate staging and production environments.
#
# CUSTOMIZATION REQUIRED:
# 1. Update PROJECT_ID variables with your actual GCP project IDs
# 2. Update REPOSITORY names to match your Artifact Registry repositories
# 3. Update SERVICE_NAME values to match your Cloud Run services
# 4. Modify REGION if using a different GCP region
# 5. Add/remove environment variables based on your application needs
# 6. Adjust resource limits based on your application requirements
#
# BRANCH STRATEGY:
# - develop branch: triggers staging deployment
# - main/master branch: triggers production deployment
# =================================================================
# Define pipeline stages
stages:
- build # Build Docker images with environment-specific configurations
- deploy # Deploy to Google Cloud Run environments
# Global variables for the entire pipeline
variables:
# Docker configuration for improved performance
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
# Google Cloud configuration
# CUSTOMIZE: Change to your preferred GCP region
REGION: us-central1
# Docker build configuration
DOCKER_BUILDKIT: 1
# =================================================================
# STAGING ENVIRONMENT
# Triggers on: develop branch pushes
# =================================================================
# Build Docker image for staging environment
build_staging:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
variables:
# CUSTOMIZE: Replace with your staging project details
PROJECT_ID: your-staging-project-id
REPOSITORY: your-app-staging
SERVICE_NAME: your-app-staging
IMAGE_NAME: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${SERVICE_NAME}:${CI_COMMIT_SHA}
IMAGE_NAME_LATEST: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${SERVICE_NAME}:latest
before_script:
# Authenticate with Google Artifact Registry
- echo "Authenticating with Google Artifact Registry..."
- echo "$STAGING_GCP_SERVICE_ACCOUNT" > gcp-key.json
- cat gcp-key.json | docker login -u _json_key --password-stdin https://${REGION}-docker.pkg.dev
- echo "Authentication successful"
script:
# Build Docker image with staging environment variables
- echo "Building Docker image for staging environment..."
- |
docker build \
--build-arg API_BASE_URL="$STAGING_API_BASE_URL" \
--build-arg WEB_BASE_URL="$STAGING_WEB_BASE_URL" \
--build-arg DATABASE_URL="$STAGING_DATABASE_URL" \
--build-arg NEXTAUTH_SECRET="$STAGING_NEXTAUTH_SECRET" \
--build-arg NEXTAUTH_URL="$STAGING_NEXTAUTH_URL" \
-t $IMAGE_NAME \
-t $IMAGE_NAME_LATEST .
# Push images to Artifact Registry
- echo "Pushing images to Artifact Registry..."
- docker push $IMAGE_NAME
- docker push $IMAGE_NAME_LATEST
- echo "Image push completed successfully"
after_script:
# Clean up sensitive files
- rm -f gcp-key.json
- docker system prune -f
# Trigger conditions
only:
- develop
# Retry configuration for build failures
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
# Deploy staging image to Google Cloud Run
deploy_staging:
stage: deploy
image: google/cloud-sdk:alpine
variables:
# CUSTOMIZE: Replace with your staging project details
PROJECT_ID: your-staging-project-id
REPOSITORY: your-app-staging
SERVICE_NAME: your-app-staging
IMAGE_NAME: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${SERVICE_NAME}:${CI_COMMIT_SHA}
before_script:
# Authenticate with Google Cloud
- echo "Authenticating with Google Cloud..."
- echo "$STAGING_GCP_SERVICE_ACCOUNT" > gcp-key.json
- gcloud auth activate-service-account --key-file=gcp-key.json
- gcloud config set project $PROJECT_ID
- echo "Authentication successful"
script:
# Deploy to Google Cloud Run
- echo "Deploying to Cloud Run staging environment..."
- |
gcloud run deploy $SERVICE_NAME \
--image $IMAGE_NAME \
--platform managed \
--region $REGION \
--allow-unauthenticated \
--memory 1Gi \
--cpu 1 \
--timeout 300s \
--max-instances 10 \
--min-instances 0 \
--concurrency 80 \
--set-env-vars "API_BASE_URL=$STAGING_API_BASE_URL,WEB_BASE_URL=$STAGING_WEB_BASE_URL,DATABASE_URL=$STAGING_DATABASE_URL,NEXTAUTH_SECRET=$STAGING_NEXTAUTH_SECRET,NEXTAUTH_URL=$STAGING_NEXTAUTH_URL"
# Get service URL
- SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --region=$REGION --format="value(status.url)")
- echo "Staging deployment completed successfully!"
- echo "Service URL: $SERVICE_URL"
after_script:
# Clean up sensitive files
- rm -f gcp-key.json
# Trigger conditions
only:
- develop
# Dependency on build stage
needs:
- build_staging
# =================================================================
# PRODUCTION ENVIRONMENT
# Triggers on: main/master branch pushes
# =================================================================
# Build Docker image for production environment
build_production:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
variables:
# CUSTOMIZE: Replace with your production project details
PROJECT_ID: your-production-project-id
REPOSITORY: your-app-production
SERVICE_NAME: your-app-production
IMAGE_NAME: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${SERVICE_NAME}:${CI_COMMIT_SHA}
IMAGE_NAME_LATEST: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${SERVICE_NAME}:latest
before_script:
# Authenticate with Google Artifact Registry
- echo "Authenticating with Google Artifact Registry..."
- echo "$PROD_GCP_SERVICE_ACCOUNT" > gcp-key-prod.json
- cat gcp-key-prod.json | docker login -u _json_key --password-stdin https://${REGION}-docker.pkg.dev
- echo "Authentication successful"
script:
# Build Docker image with production environment variables
- echo "Building Docker image for production environment..."
- |
docker build \
--build-arg API_BASE_URL="$PROD_API_BASE_URL" \
--build-arg WEB_BASE_URL="$PROD_WEB_BASE_URL" \
--build-arg DATABASE_URL="$PROD_DATABASE_URL" \
--build-arg NEXTAUTH_SECRET="$PROD_NEXTAUTH_SECRET" \
--build-arg NEXTAUTH_URL="$PROD_NEXTAUTH_URL" \
-t $IMAGE_NAME \
-t $IMAGE_NAME_LATEST .
# Push images to Artifact Registry
- echo "Pushing images to Artifact Registry..."
- docker push $IMAGE_NAME
- docker push $IMAGE_NAME_LATEST
- echo "Image push completed successfully"
after_script:
# Clean up sensitive files
- rm -f gcp-key-prod.json
- docker system prune -f
# Trigger conditions - customize based on your main branch name
only:
- main # Change to 'master' if that's your default branch
# Retry configuration for build failures
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
# Deploy production image to Google Cloud Run
deploy_production:
stage: deploy
image: google/cloud-sdk:alpine
variables:
# CUSTOMIZE: Replace with your production project details
PROJECT_ID: your-production-project-id
REPOSITORY: your-app-production
SERVICE_NAME: your-app-production
IMAGE_NAME: ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPOSITORY}/${SERVICE_NAME}:${CI_COMMIT_SHA}
before_script:
# Authenticate with Google Cloud
- echo "Authenticating with Google Cloud..."
- echo "$PROD_GCP_SERVICE_ACCOUNT" > gcp-key-prod.json
- gcloud auth activate-service-account --key-file=gcp-key-prod.json
- gcloud config set project $PROJECT_ID
- echo "Authentication successful"
script:
# Deploy to Google Cloud Run with production configuration
- echo "Deploying to Cloud Run production environment..."
- |
gcloud run deploy $SERVICE_NAME \
--image $IMAGE_NAME \
--platform managed \
--region $REGION \
--allow-unauthenticated \
--memory 2Gi \
--cpu 2 \
--timeout 900s \
--max-instances 100 \
--min-instances 1 \
--concurrency 1000 \
--set-env-vars "API_BASE_URL=$PROD_API_BASE_URL,WEB_BASE_URL=$PROD_WEB_BASE_URL,DATABASE_URL=$PROD_DATABASE_URL,NEXTAUTH_SECRET=$PROD_NEXTAUTH_SECRET,NEXTAUTH_URL=$PROD_NEXTAUTH_URL"
# Get service URL
- SERVICE_URL=$(gcloud run services describe $SERVICE_NAME --region=$REGION --format="value(status.url)")
- echo "Production deployment completed successfully!"
- echo "Service URL: $SERVICE_URL"
after_script:
# Clean up sensitive files
- rm -f gcp-key-prod.json
# Trigger conditions - customize based on your main branch name
only:
- main # Change to 'master' if that's your default branch
# Dependency on build stage
needs:
- build_production
# Manual approval for production deployments (optional)
when: manual
allow_failure: false
# =================================================================
# OPTIONAL: Additional Jobs
# =================================================================
# Run tests before deployment (optional)
test:
stage: build
image: node:18-alpine
before_script:
- npm ci
script:
- npm run test
- npm run lint
only:
- develop
- main
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
# Security scanning (optional)
security_scan:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
script:
- docker build -t temp-image .
- docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest image temp-image
allow_failure: true
only:
- develop
- main
To add new environment variables for your specific services:
STAGING_YOUR_VARIABLE and
PROD_YOUR_VARIABLE
ARG
and ENV statements--build-arg and --set-env-vars sections
| Application Type | Memory | CPU | Max Instances |
|---|---|---|---|
| Small Application | 512Mi | 1 | 10 |
| Medium Application | 1Gi | 1 | 50 |
| Large Application | 2Gi | 2 | 100 |
| Enterprise Application | 4Gi | 4 | 1000 |
Common services you might want to add:
| Error | Cause | Solution |
|---|---|---|
| Docker authentication failed | Invalid service account credentials | Verify service account key in GitLab variables |
| Build context too large | Large files in build context | Update .dockerignore file |
| NPM install fails | Missing dependencies or network issues | Check package.json and npm registry access |
| Error | Cause | Solution |
|---|---|---|
| Insufficient permissions | Service account lacks required roles | Verify IAM permissions for service account |
| Resource allocation errors | Insufficient memory or CPU | Adjust resource limits in deployment script |
| Application startup failures | Missing environment variables | Verify all required environment variables are set |