GitLab CI/CD Pipeline for Next.js on Google Cloud Run

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.

15 min setup
Fully customizable
Production ready
By Suraj Anand Technical Consultant at EkaivaKriti
Last updated: July 6, 2025

Table of Contents

Overview

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.

Prerequisites

1. Google Cloud Platform Setup

Create GCP Projects

  1. Create two separate GCP projects: one for staging and one for production
  2. Note down both project IDs for later configuration
  3. Ensure you have appropriate billing enabled for both projects

Enable Required APIs

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 Artifact Registry Repositories

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 Service Accounts

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
          

Grant Required Permissions

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"
          

Download Service Account Keys

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
          

GitLab CI/CD Variables Setup

Required Service Account Variables

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

Environment-Specific Variables

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
Customization Note: The above variables are examples. Add only the environment variables your application actually uses. You can add variables for any service like MongoDB, Stripe, SendGrid, etc., following the same STAGING_* and PROD_* naming pattern.

Project File Structure

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
          

Docker Configuration

Dockerfile Template

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"]
          

.dockerignore Template

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/
          

GitLab CI/CD Pipeline Configuration

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
          
Important Customization Notes:
  • Replace all placeholder values (your-staging-project-id, your-production-project-id, etc.) with your actual project details
  • Modify the environment variables section to match your application's specific requirements
  • Adjust resource limits (memory, CPU) based on your application's needs
  • Update branch names if your default branch is not 'main'
  • Consider enabling/disabling optional jobs based on your requirements

Customization Guide

Adding Custom Environment Variables

To add new environment variables for your specific services:

  1. Add to GitLab CI/CD Variables: Create variables following the pattern STAGING_YOUR_VARIABLE and PROD_YOUR_VARIABLE
  2. Update Dockerfile: Add corresponding ARG and ENV statements
  3. Update .gitlab-ci.yml: Add the variables to the --build-arg and --set-env-vars sections

Resource Configuration Examples

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

Adding Custom Services

Common services you might want to add:

  • Database Services: PostgreSQL, MySQL, MongoDB
  • Cache Services: Redis, Memcached
  • Authentication: Auth0, Firebase Auth, Supabase
  • Payment Processing: Stripe, PayPal, Square
  • Email Services: SendGrid, Mailgun, Amazon SES
  • File Storage: AWS S3, Google Cloud Storage
  • Monitoring: Sentry, LogRocket, Datadog
  • Analytics: Google Analytics, Mixpanel, Segment

Security Best Practices

Service Account Management

  • Use separate service accounts for staging and production
  • Apply principle of least privilege for permissions
  • Regularly rotate service account keys
  • Never commit service account keys to version control

Environment Variable Security

  • Use GitLab CI/CD variables for all sensitive data
  • Mark sensitive variables as "Masked" in GitLab
  • Use different values for staging and production
  • Regularly audit and rotate secrets

Container Security

  • Use multi-stage Docker builds to minimize image size
  • Run containers as non-root users
  • Keep base images updated
  • Include security scanning in your pipeline

Monitoring and Maintenance

Monitoring Your Deployments

  • GitLab CI/CD: Monitor pipeline status and logs
  • Google Cloud Console: Monitor Cloud Run metrics and logs
  • Application Monitoring: Use tools like Sentry for error tracking
  • Performance Monitoring: Monitor response times and resource usage

Maintenance Tasks

  • Regularly update Docker base images
  • Monitor and optimize resource usage
  • Review and update environment variables
  • Audit service account permissions
  • Monitor costs and optimize resources

Troubleshooting Common Issues

Build Failures

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

Deployment Failures

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

Runtime Issues

  • Container crashes: Check Cloud Run logs for error messages
  • Performance issues: Monitor resource usage and adjust limits
  • Network connectivity: Verify firewall rules and VPC configurations
  • Database connections: Check connection strings and network access

Additional Resources

Documentation

  • Google Cloud Run: https://cloud.google.com/run/docs
  • GitLab CI/CD: https://docs.gitlab.com/ee/ci/
  • Next.js Deployment: https://nextjs.org/docs/deployment
  • Docker Best Practices: https://docs.docker.com/develop/dev-best-practices/

Community Support

  • Stack Overflow: Search for specific error messages
  • GitHub Issues: Check relevant project repositories
  • Discord/Slack: Join developer communities for real-time help
  • Google Cloud Community: Access Google Cloud forums and support
Final Note: This template provides a solid foundation for deploying Next.js applications to Google Cloud Run. Customize it based on your specific requirements, and always test thoroughly in staging before deploying to production. Remember to follow security best practices and maintain regular monitoring of your deployments.

Created with care for the developer community

EkaivaKriti Logo
EkaivaKriti
Digital Solutions & App Development Partner
© 2025 EkaivaKriti. All rights reserved.