How to Set Up Jenkins for Continuous Deployment on Google Cloud Platform (GCP)

How to Set Up Jenkins for Continuous Deployment on Google Cloud Platform (GCP)

Google Cloud has excellent native CI/CD tooling in Cloud Build, but plenty of teams — including ones I’ve worked with — already have deep Jenkins investment and just want to point it at GCP instead of ripping everything out. The good news is Jenkins integrates cleanly with GCP through service accounts and the gcloud CLI, and the same pipeline patterns you’d use anywhere else apply here too. This guide covers setting up Jenkins for deployments to Google Kubernetes Engine (GKE) and Cloud Run, the two most common GCP compute targets.

Jenkins Architecture on GCP

Prerequisites

Step 1: Create a Service Account for Jenkins

gcloud iam service-accounts create jenkins-deployer \
  --display-name="Jenkins CI/CD Deployer"

gcloud projects add-iam-policy-binding my-gcp-project \
  --member="serviceAccount:jenkins-deployer@my-gcp-project.iam.gserviceaccount.com" \
  --role="roles/run.admin"

gcloud projects add-iam-policy-binding my-gcp-project \
  --member="serviceAccount:jenkins-deployer@my-gcp-project.iam.gserviceaccount.com" \
  --role="roles/container.developer"

gcloud projects add-iam-policy-binding my-gcp-project \
  --member="serviceAccount:jenkins-deployer@my-gcp-project.iam.gserviceaccount.com" \
  --role="roles/artifactregistry.writer"

Generate a key only if you’re not using Workload Identity Federation (preferred for GCE-hosted Jenkins):

gcloud iam service-accounts keys create jenkins-key.json \
  --iam-account=jenkins-deployer@my-gcp-project.iam.gserviceaccount.com

Store jenkins-key.json in Jenkins Credentials as a “Secret file.”

Step 2: Install Plugins

Step 3: Install the GCP CLI Tools on the Agent

curl https://sdk.cloud.google.com | bash
exec -l $SHELL
gcloud components install kubectl gke-gcloud-auth-plugin

Step 4: Write the Jenkinsfile for GKE Deployment

pipeline {
    agent any

    environment {
        PROJECT_ID = 'my-gcp-project'
        CLUSTER_NAME = 'myapp-cluster'
        CLUSTER_ZONE = 'us-central1-a'
        IMAGE = "us-central1-docker.pkg.dev/${PROJECT_ID}/myapp-repo/myapp:${env.BUILD_NUMBER}"
    }

    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/yourorg/myapp.git'
            }
        }

        stage('Authenticate to GCP') {
            steps {
                withCredentials([file(credentialsId: 'gcp-jenkins-key', variable: 'GCP_KEY')]) {
                    sh '''
                        gcloud auth activate-service-account --key-file=$GCP_KEY
                        gcloud config set project $PROJECT_ID
                    '''
                }
            }
        }

        stage('Build and Push Image') {
            steps {
                sh '''
                    gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
                    docker build -t $IMAGE .
                    docker push $IMAGE
                '''
            }
        }

        stage('Run Tests') {
            steps {
                sh 'npm test'
            }
        }

        stage('Get GKE Credentials') {
            steps {
                sh '''
                    gcloud container clusters get-credentials $CLUSTER_NAME \
                      --zone $CLUSTER_ZONE --project $PROJECT_ID
                '''
            }
        }

        stage('Deploy to GKE') {
            steps {
                sh '''
                    kubectl set image deployment/myapp myapp=$IMAGE -n production
                    kubectl rollout status deployment/myapp -n production --timeout=180s
                '''
            }
        }
    }

    post {
        failure {
            sh 'kubectl rollout undo deployment/myapp -n production || true'
            echo 'Deployment failed — rolled back automatically.'
        }
    }
}

Step 5: Deploying to Cloud Run Instead

Cloud Run is often simpler for stateless services since there’s no cluster to manage:

stage('Deploy to Cloud Run') {
    steps {
        withCredentials([file(credentialsId: 'gcp-jenkins-key', variable: 'GCP_KEY')]) {
            sh '''
                gcloud auth activate-service-account --key-file=$GCP_KEY
                gcloud run deploy myapp-service \
                  --image=$IMAGE \
                  --region=us-central1 \
                  --platform=managed \
                  --allow-unauthenticated
            '''
        }
    }
}

Cloud Run also supports traffic splitting natively for canary-style rollouts:

stage('Canary Deploy to Cloud Run') {
    steps {
        sh '''
            gcloud run deploy myapp-service \
              --image=$IMAGE \
              --region=us-central1 \
              --no-traffic \
              --tag=canary

            gcloud run services update-traffic myapp-service \
              --region=us-central1 \
              --to-tags=canary=10
        '''
    }
}

This deploys the new revision with zero traffic initially, tags it, then shifts 10% of traffic to it — letting you validate before a full promotion with gcloud run services update-traffic myapp-service --to-latest.

Step 6: Using Workload Identity Federation Instead of Keys

For Jenkins hosted on GCE or GKE, avoid service account keys entirely by using Workload Identity:

gcloud iam workload-identity-pools create jenkins-pool \
  --location="global"

gcloud iam workload-identity-pools providers create-oidc jenkins-provider \
  --location="global" \
  --workload-identity-pool="jenkins-pool" \
  --issuer-uri="https://your-jenkins-url/oidc" \
  --attribute-mapping="google.subject=assertion.sub"

This lets Jenkins authenticate using short-lived OIDC tokens instead of a long-lived JSON key file — a meaningful security improvement worth the extra setup for production environments.

Real-World Workflow

  1. A commit merges to main; Jenkins authenticates to GCP using the service account and builds a container image.
  2. The image is pushed to Artifact Registry, tagged with the Jenkins build number for traceability.
  3. Jenkins deploys the new revision to Cloud Run with no traffic, or updates the GKE deployment image.
  4. For GKE, kubectl rollout status blocks until the rollout completes or times out; for Cloud Run, a canary tag receives a small percentage of traffic first.
  5. A monitoring stage checks Cloud Monitoring/Logging for error spikes before promoting to 100% traffic.
  6. If anything fails, the post { failure } block automatically rolls back (kubectl rollout undo for GKE, traffic revert for Cloud Run).

Security Best Practices

Troubleshooting

Monitoring Deployments with Cloud Monitoring and Logging

Once a deployment lands, it’s worth having Jenkins confirm the application is actually healthy using GCP’s native observability tools rather than just trusting a rollout status check. Query Cloud Monitoring right after a deploy to compare error rates before and after:

stage('Post-Deploy Health Check via Cloud Monitoring') {
    steps {
        withCredentials([file(credentialsId: 'gcp-jenkins-key', variable: 'GCP_KEY')]) {
            sh '''
                gcloud auth activate-service-account --key-file=$GCP_KEY
                ERROR_COUNT=$(gcloud logging read \
                  'resource.type="cloud_run_revision" AND severity>=ERROR' \
                  --project=$PROJECT_ID --freshness=5m --format='value(timestamp)' | wc -l)
                echo "Errors in last 5 minutes: $ERROR_COUNT"
                if [ "$ERROR_COUNT" -gt 20 ]; then
                    echo "Error count too high after deploy"
                    exit 1
                fi
            '''
        }
    }
}

A failed check here can trigger the same automatic rollback pattern used in the GKE stage — undoing the rollout or reverting Cloud Run traffic before the on-call engineer even gets paged.

Managing Multiple GCP Projects (Dev, Staging, Production)

Most organizations split environments across separate GCP projects rather than namespaces within a single project, which gives cleaner IAM boundaries and billing separation. Parametrize the pipeline so the same Jenkinsfile deploys to different projects depending on the branch or a build parameter:

parameters {
    choice(name: 'TARGET_ENV', choices: ['dev', 'staging', 'production'], description: 'Target GCP project')
}

environment {
    PROJECT_ID = "${params.TARGET_ENV == 'production' ? 'myapp-prod' : params.TARGET_ENV == 'staging' ? 'myapp-staging' : 'myapp-dev'}"
}

Pair each environment with its own service account and credentials entry in Jenkins so a mistaken deploy target can never accidentally touch production using dev-scoped permissions.

FAQs

Should I use Cloud Build instead of Jenkins on GCP? Cloud Build is a solid managed option with less operational overhead, but Jenkins offers more plugin flexibility, better multi-cloud portability, and is the right choice if you already have Jenkins expertise and infrastructure elsewhere in your organization.

Can Jenkins deploy to GKE Autopilot clusters the same way? Yes, the kubectl commands are identical; Autopilot just manages node provisioning for you, which doesn’t change how Jenkins interacts with the cluster.

How do I handle secrets that my application needs at runtime on GCP? Use Google Secret Manager and have your application fetch secrets at startup or runtime, rather than baking them into the container image or Kubernetes manifests as plaintext.

What’s the fastest way to roll back a bad GKE deployment? kubectl rollout undo deployment/myapp -n production reverts to the previous ReplicaSet almost immediately, assuming the previous version is still within the deployment’s revision history limit.

Summary

Jenkins on GCP boils down to solid service account scoping (or better, Workload Identity Federation), the gcloud/kubectl CLI tools on your agents, and deployment stages tailored to GKE or Cloud Run depending on your architecture. Both targets support safe rollout patterns — rolling updates with automatic rollback on GKE, and native traffic-splitting canaries on Cloud Run — that plug directly into a standard Jenkins pipeline.

References

Exit mobile version