Scalable Deployment
Scalable and Zero-Downtime Deployments with Managed Instance Groups and CI/CD
This document summarizes a technical discussion about how to deploy, scale, and update applications using managed instance groups (MIG) — collections of identical servers that are managed as a single unit. It explains the concepts of immutable infrastructure, rolling updates, and zero-downtime deployments, showing how new versions of an application can be released safely without disrupting active users.
Overview
First let's see the overview of the topic. These are our goals that we want to achieve with Google Cloud (If you worked with other cloud providers, you can still follow this document. Usually there are equivalent features in other cloud providers):
- automatically with CI
- autoscale feature
- no downtime during deployment
Steps
Push & CI/CD
The first step is pushing our code. First make sure to increase your version in your package.json file. We are going to need this new version in the next steps. Then push your code to the repository. We are going to use gitlab ci or GitHub Actions for CI/CD. Note that we can get this version (e.g 1.0.0) from the package.json file as following: VERSION=$(node -p "require('./package.json').version").
npm version minorThen just push your code.
Init Google CLI
Now we need to be able to run gcloud commands. We are going to use gcloud to manage our instance groups. It depends on your ci/cd tool.
deploy_to_gcp:
stage: deploy
image: google/cloud-sdk:latest
variables:
VERSION: ""
script:
# 1. Install Node.js (required to read package.json version)
- curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
- apt-get install -y nodejs
# 2. Get version from package.json
- VERSION=$(node -p "require('./package.json').version")
- echo "VERSION=$VERSION"
- export VERSION
# 3. Authenticate with GCP
- echo "$GCP_SA_KEY" > gcp-key.json
- gcloud auth activate-service-account --key-file=gcp-key.json
- gcloud config set project $GCP_PROJECT# Checkout is not automatic in GitHub Actions
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Get version
id: vars
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Authenticate gcloud
uses: google-github-actions/auth@v2
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}Docker build
Now we need to build our docker image. We are going to use docker command line to build our docker image. We are going to use this build in the next steps. This is how are CI looks like:
script:
- docker build -t $IMAGE_NAME:$VERSION .
- docker push $IMAGE_NAME:$VERSION
- docker tag $IMAGE_NAME:$VERSION $IMAGE_NAME:latest
- docker push $IMAGE_NAME:latest- name: Build & push Docker image
run: |
IMAGE="europe-docker.pkg.dev/${{ secrets.GCP_PROJECT }}/my-repo/nodejs-app"
docker build -t $IMAGE:${{ steps.vars.outputs.version }} .
docker push $IMAGE:${{ steps.vars.outputs.version }}
docker tag $IMAGE:${{ steps.vars.outputs.version }} $IMAGE:latest
docker push $IMAGE:latestCreate instance template
Now we need to create an instance template. We are going to use gcloud command line to create an instance template. Unlike a traditional VM where you SSH and deploy code manually, MIGs are immutable:
You don’t “update” running instances.
Instead, you create a new machine definition (instance template) and let the MIG replace old instances with new ones.
script:
# Create new instance template
- TEMPLATE_NAME="nodejs-app-template-$VERSION"
- gcloud compute instance-templates create-with-container $TEMPLATE_NAME \
--machine-type=e2-medium \
--boot-disk-size=20GB \
--container-image=$IMAGE_NAME:$VERSION \
--tags=http-server- name: Create new instance template
run: |
TEMPLATE_NAME="nodejs-app-template-${{ steps.vars.outputs.version }}"
IMAGE="europe-docker.pkg.dev/${{ secrets.GCP_PROJECT }}/my-repo/nodejs-app:${{ steps.vars.outputs.version }}"
gcloud compute instance-templates create-with-container $TEMPLATE_NAME \
--machine-type=e2-medium \
--boot-disk-size=20GB \
--container-image=$IMAGE \
--tags=http-serverRollout and Deployment
Now we need to rollout and deploy our instance template. We are going to use gcloud command line to rollout and deploy our instance template.
Rollout strategies in MIG
You control rollout with update policies when you apply a new template.
-
Rolling Update (most common). MIG replaces instances in batches. Example:
- You have 10 instances.
- Policy: maxSurge = 2, maxUnavailable = 1.
- MIG creates 2 new VMs (surge) → waits until they’re healthy → deletes 1 old VM (unavailable).
- Repeats until all old ones are gone.
✅ Benefits: Continuous capacity (users never lose all servers). Smooth, controlled rollout.
-
Blue/Green (Replace All at Once)
- MIG creates a full set of new instances alongside the old ones (using maxSurge = N).
- Once all new instances are healthy → it deletes the old ones.
✅ Quick switchover. Easier rollback (just switch traffic back).
❌ Requires extra capacity during rollout.
-
Canary Update
- You replace just a few instances first (say 1–2).
- Monitor them for issues.
- If stable, continue with full rollout.
✅ Safer — catch issues early. Works great with monitoring & alerting.
❌ Unequal User Experience (Some users may hit old instances, others hit the canary instances.)
script:
# Update MIG and start rolling update
- gcloud compute instance-groups managed set-instance-template my-app-mig \
--template=$TEMPLATE_NAME
- gcloud compute instance-groups managed rolling-action start-update my-app-mig \
--version template=$TEMPLATE_NAME \
--max-unavailable=1 \
--max-surge=1- name: Update MIG and start rolling update
run: |
gcloud compute instance-groups managed set-instance-template my-app-mig \ --template=nodejs-app-template-${{ steps.vars.outputs.version }} gcloud compute instance-groups managed rolling-action start-update my-app-mig \ --version template=nodejs-app-template-${{ steps.vars.outputs.version }} \ --max-unavailable=1 \ --max-surge=1Summary
-
Build your code into an image or startup process
- Use Google Compute Engine instance templates that include your app.
- Or use a Container-Optimized OS instance that pulls your Docker image at startup.
- Or provide a startup script that installs your code/artifacts when the VM boots.
-
Create a new Instance Template Define a new template (with new image, new startup script, or updated container tag). This template becomes the source for new VMs.
-
Update your MIG to use the new template Tell the MIG: "start using this new template."
-
Rolling update MIG will gradually replace old VMs with new ones (based on your rollout policy). During rollout, the load balancer only routes traffic to healthy VMs.