Multi-Cloud Disaster Recovery with Pulumi
Build 3-way failover across AWS and GCP managed entirely with Infrastructure as Code using Pulumi
Introduction
This project has been archived
We're no longer updating this project - we recommend checking out our active projects instead!
If you've already completed this project, don't worry! Your documentation is still available for you to check out and share.
If you have any questions, leave a question in our Discord and we'll get back to you ASAP!
⚡️ 30 second Summary
Imagine your production application runs on AWS App Runner across two regions. Good start - but what happens if AWS itself has an outage? Your multi-region setup becomes useless because it's still single-cloud.
True disaster recovery means multi-cloud. And manual deployments mean configuration drift - one region drifts from another, and suddenly your "identical" environments aren't identical anymore.
You'll solve both problems by managing AWS and GCP infrastructure with Pulumi. Every change is version-controlled and reproducible. Every deployment is consistent across clouds.
How do the pieces fit together?
In this project, you'll create a multi-cloud failover system with the same web application running on AWS App Runner and GCP Cloud Run - all managed with Infrastructure as Code using Pulumi TypeScript.
Why Infrastructure as Code?
When you deploy infrastructure manually through cloud consoles, there's no record of what you did. Team members make different changes in different regions. Six months later, your "identical" environments have drifted apart.
Infrastructure as Code solves this by defining your infrastructure in version-controlled files. Every change is tracked, reviewed, and reproducible. If something breaks, you can see exactly what changed and roll back.
Pulumi takes this further by letting you use real programming languages like TypeScript instead of domain-specific languages. You get loops, conditionals, and IDE autocomplete for your infrastructure.
You don't need prior Pulumi or multi-cloud experience. This project teaches you how to import existing infrastructure, deploy across cloud providers, and configure CloudFront failover.
Not sure if this project is right for you? Check if it matches your goals
What you'll create:
- Pulumi TypeScript code managing your AWS and GCP infrastructure from a single codebase.
- Multi-cloud failover with CloudFront that automatically switches traffic between AWS and GCP.
- 💎 Secret Mission: A CloudWatch dashboard built with Cursor + Pulumi that shows your DR system health at a glance.
Prerequisite
This project builds on Part 1: Multi-Region Deployment and Part 2: CloudFront Failover. You'll need App Runner services in us-east-1 and us-west-2, plus a CloudFront distribution with origin failover.
Don't have those yet? No worries - there's a quick setup guide in Step #1 if you need it!
If you're up for a bit of a challenge, quiz yourself on the key concepts up ahead in this project! Don't be too nervous, you'll also get a quiz at the end of this project, to recap what you've learned.
This project is part of a series:
- Part 1: You are here!
- Part 2: CloudFront Failover
- Part 3: Multi-Cloud with Pulumi
Import Existing AWS Infrastructure into Pulumi
Time to bring your existing AWS infrastructure under code control! You'll install Pulumi and import your App Runner services into TypeScript code.
In this step, get ready to:
- Install Pulumi CLI and initialize a TypeScript project
- Import your App Runner services (us-east-1 and us-west-2)
- Verify your code matches the deployed infrastructure
Check Your App Runner Services
Before we start with Pulumi, let's verify your App Runner services from Parts 1 and 2 are still running.
- Go to AWS App Runner Console.
- Make sure your region is set to US East (N. Virginia) in the top right.
What do you see?
✅ I see my service running
Your us-east-1 service is ready.
- Right-click on your us-east-1 service
- Click 'Copy Link Address.'
- URL and save it here: Your us-east-1 App Runner URL
- Now switch to US West (Oregon) region using the dropdown in the top right.
- Right-click on your us-west-2 service
- Click 'Copy Link Address.'
- Enter its URL here: Your us-west-2 App Runner URL
- Now go to the CloudFront Console.
- Copy your distribution's Domain name (looks like d1234abcd5678.cloudfront.net).
Save your CloudFront URL here: Your CloudFront URL (e.g. d1234abcd5678.cloudfront.net)
Once both App Runner services show Running status, you're ready to proceed!
❌ I haven't done Projects 1 and 2
No problem! Let's quickly set up the required infrastructure before starting with Pulumi.
Part 1: Set Up GitHub Repository
- Go to github.com and sign in (or create an account if needed).
- Click your profile picture → Repositories → New.
- Name it multi-region-app, select Private, and click Create repository.
- Copy the HTTPS URL from the quick setup page.
Paste your repository URL here: [[YOUR_REPO_URL="https://github.com/YOUR_USERNAME/multi-region-app.git"]]
Clone and Create Your App with Cursor
- If you don't have Cursor installed, download it from cursor.com.
- Open Cursor and press Cmd+Shift+P (Mac) or Ctrl+Shift+P (Windows) to open the command palette.
- Type Git: Clone, press Enter, and paste your repo URL: [[YOUR_REPO_URL]]
- Select a folder to clone into, then click Open when prompted.
✅ It's working
Great! Your repository is cloned and ready. Let's move on.
❌ Getting an error?
That's totally okay! This just means you don't have Git installed on your computer.
- Head over to git-scm.com/downloads.
- Download and run the installer for your operating system.
- Once installed, restart your IDE and try again.
Still having issues? Troubleshoot here or share your error with the NextWork community!
Create Your Express App with AI
- Open Cursor chat (Cmd+L / Ctrl+L) and send this prompt:
Create a simple Express.js web server I can deploy to AWS App Runner. I need a package.json with the Express dependency and an index.js that listens on process.env.PORT (or 8080 as fallback). Just a single route at "/" that returns "Hello from AWS!" is fine. Make sure there's a start script so App Runner can run it.
- Click Accept to add the files.
Push to GitHub
- Open the terminal in Cursor (Ctrl+`) and run:
git add .
git commit -m "Add Express app"
git push
✅ It's working
Great! Your code is pushed to GitHub. Let's move on.
❌ Getting an error?
Getting an authentication error?
Install GitHub CLI, run gh auth login, then try git push again.
Get help with GitHub authentication or share your error with the NextWork community!
Deploy to App Runner (us-east-1)
- Go to App Runner in the us-east-1 region.
- Click Create service.
- Select Source code repository and GitHub as provider, then click Add new.
- Name the connection github-connection-east and click Install another.
- In GitHub, select your multi-region-app repository and click Install & Authorize.
- Back in AWS, select your GitHub app and click Next.
- Select your repository and main branch.
- Select Automatic deployment and click Next.
- Configure build: Runtime = Nodejs 18, Build command = npm install, Start command = npm start, Port = 8080.
- Click Next.
- Name the service multi-region-app-east.
- Click Next, then Create & deploy.
Wait 2-3 minutes for deployment. Once Running, copy the Default domain URL.
Save your us-east-1 URL here: Your us-east-1 App Runner URL
Deploy to App Runner (us-west-2)
- Switch to the us-west-2 region in the AWS Console (top right corner).
- Go to App Runner and click Create service.
- Select Source code repository and GitHub as provider, then click Add new.
- Name the connection github-connection-west.
- Select your GitHub account from the GitHub app dropdown (already installed).
- Click Next, then select your repository and main branch.
- Select Automatic deployment and click Next.
- Configure build: Runtime = Nodejs 18, Build command = npm install, Start command = npm start, Port = 8080.
- Click Next.
- Name the service multi-region-app-west.
- Click Next, then Create & deploy.
Wait for deployment. Once Running, copy the Default domain URL.
Save your us-west-2 URL here: Your us-west-2 App Runner URL
Part 2: Create CloudFront Distribution
Now let's set up CloudFront with failover between your two App Runner services.
- Open CloudFront.
- Click Create distribution.
- Select Pay as you go pricing.
- Click Next.
- For Distribution name, enter primary-us-east-1.
- For Distribution type, select Single website or app.
- Click Next.
- For Origin type, select Other.
- For Origin domain, enter your us-east-1 App Runner URL (without https://): [[YOUR_EAST_APPRUNNER_URL]]
- For WAF, select Do not enable security protections.
- Click Create distribution.
- Wait 5-10 minutes for status to change to "Enabled".
Copy your CloudFront distribution domain name.
Save your CloudFront URL here: Your CloudFront URL (e.g. d1234abcd5678.cloudfront.net)
Add Secondary Origin
- Click on your distribution to open it.
- Go to the Origins tab → Click Create origin.
- For Origin domain, enter your us-west-2 App Runner URL (without https://): Your us-west-2 App Runner URL
- For Name, enter secondary-us-west-2.
- Click Create origin.
Create Origin Group for Failover
- Stay on the Origins tab → Click Create origin group.
- Add your primary origin (us-east-1) first, then your secondary origin (us-west-2).
- Ensure primary is listed as 1 and secondary as 2.
- For Name, enter failover-group.
- For Failover criteria, select: 404, 500, 502, 503, 504.
- Click Create origin group.
Update Cache Behavior
- Go to the Behaviors tab.
- Select the default behavior and click Edit.
- For Origin and origin groups, select failover-group.
- Set Viewer protocol policy to HTTPS only.
- Set Allowed HTTP methods to GET, HEAD, OPTIONS.
- Set Cache policy to CachingDisabled.
- Set Origin request policy to AllViewerExceptHostHeader.
- Set Response headers policy to SimpleCORS.
- Click Save changes.
- Wait 2-5 minutes for changes to deploy.
Verify Setup
- Open your CloudFront URL in a browser: https://[[YOUR_CLOUDFRONT_URL]]
- You should see "Hello from AWS!" (or your app's response).
You now have all the prerequisites! Continue with the Pulumi setup below.
Still stuck? Ask for help. You can also ask in the NextWork Community.
Install Pulumi CLI
First, let's check if you have Pulumi installed.
- Open Terminal (Mac/Linux) or PowerShell (Windows).
- Run this command:
pulumi version
What do you see?
✅ I see a version number
You already have Pulumi installed. You're ready to continue!
❌ Command not found
No problem! Let's install Pulumi.
🍎 macOS
- Run the installation command:
curl -fsSL https://get.pulumi.com | sh
What does this command do?
This downloads and runs the Pulumi installer script. The -fsSL flags tell curl to: fail silently on errors (f), show progress in a minimal way (s), follow redirects (L), and use SSL (S). The script installs Pulumi to ~/.pulumi/bin and adds it to your PATH.
- Close and reopen your terminal (or run source ~/.zshrc).
- Verify the installation:
pulumi version
You should see a version number like v3.x.x.
🖼️ Windows
- Run the installation command in PowerShell:
iwr https://get.pulumi.com/install.ps1 -UseBasicParsing | iex
- Close and reopen PowerShell.
- Verify the installation:
pulumi version
You should see a version number like v3.x.x.
Initialize Your Pulumi Project
Now let's create a Pulumi project inside your existing multi-region-app folder from Parts 1 and 2.
- Open Cursor
- Select 'Open Project'
- Navigate to your multi-region-app folder and select it.
- Open the integrated terminal (Ctrl+` or Terminal → New Terminal).
- Create a new infrastructure folder for your Pulumi code:
🍎 macOS/Linux
mkdir infrastructure && cd infrastructure
🪟 Windows
mkdir infrastructure; cd infrastructure
Why a subdirectory?
Keeping your infrastructure code in a separate folder alongside your app code is a common pattern. It keeps your project organized - the app code stays in the root, and the IaC code lives in infrastructure/. This makes it easy to manage both together in one repo.
Configure Pulumi Backend
Pulumi needs a backend to store your infrastructure state. For this project, we'll use Pulumi Cloud (free tier).
Why does Pulumi need a backend?
Imagine running pulumi up and Pulumi has no memory of what it created before - it might try to recreate everything! The backend stores "state" - a record of your deployed resources. This is how Pulumi knows "I already created this App Runner service, no changes needed." Pulumi Cloud stores this state securely in the cloud (and it's free for personal projects).
✅ I already have a Pulumi account
Log in from your terminal:
🆕 I need to create an account
- Create a Pulumi account at app.pulumi.com.
- Select GitHub to sign up with your GitHub account.
Once you have created a Pulumi account with GitHub, log in from your terminal:
pulumi login
- Follow the prompts to authenticate with your browser.
You will see a 'Welcome to Pulumi!' message, this means it worked.
You should now see your Pulumi dashboard when you visit app.pulumi.com.
Browser didn't open?
If the browser doesn't open automatically, copy the URL from the terminal output and paste it into your browser manually. After authenticating, you may need to copy a token back to the terminal.
Create the Pulumi Project
- Initialize a new Pulumi TypeScript project:
pulumi new aws-typescript
What is pulumi new?
This is like npm init but for infrastructure! It creates a starter project with everything you need: a TypeScript config, the AWS libraries, and an index.ts file where you'll define your cloud resources. You'll fill in a few details like project name and AWS region, and Pulumi handles the rest.
- Answer the prompts:
- Project name: multi-region-app-infra
- Project description: Multi-cloud disaster recovery infrastructure
- Stack name: dev
- AWS region: us-east-1
- Package Manager: npm
- After you select stack name, you will be asked to select a region. Press enter to accept the default.
Pulumi will create the project structure with index.ts, Pulumi.yaml, and package.json inside your infrastructure folder.
What is a Pulumi stack?
A stack is an isolated, independently configurable instance of your Pulumi program. Think of it like environments: you might have dev, staging, and prod stacks, each with their own configuration and deployed resources. For this project, we use a single dev stack.
Import App Runner Service (us-east-1)
Now let's import your existing App Runner service from the primary region. This tells Pulumi to manage the existing resource without recreating it.
- Go to the App Runner Console in us-east-1.
- Click on your service name.
- Copy the Service ARN from the service details page.
Save your us-east-1 Service ARN here: Your us-east-1 App Runner Service ARN
- Import the resource into Pulumi.
pulumi import aws:apprunner/service:Service primary-apprunner [[YOUR_EAST_SERVICE_ARN="Your us-east-1 App Runner Service ARN"]]
- Confirm the import when prompted.
- Select Yes.
What does pulumi import do?
Think of import as "adopting" an existing resource. Pulumi reads what you've already built in AWS and generates TypeScript code that matches it exactly. The magic? Your actual infrastructure stays untouched - no recreation, no downtime. This is how teams migrate to Infrastructure as Code without starting from scratch.
Pulumi will output TypeScript code for the imported resource.
- Select the generated code from your terminal output and paste it into your index.ts file.
Import failed?
Common issues:
- (1) The ARN is incorrect - double-check it with aws apprunner list-services.
- (2) AWS credentials aren't configured - run aws configure first.
- (3) The resource is in a different region - make sure your Pulumi stack's AWS region matches where the resource lives.
Import App Runner Service (us-west-2)
Repeat the import for your secondary region App Runner service.
- Go to the App Runner Console and switch to us-west-2 region.
- Click on your service name.
- Copy the Service ARN from the service details page.
Save your us-west-2 Service ARN here: Your us-west-2 App Runner Service ARN
- Import the resource.
pulumi import aws:apprunner/service:Service secondary-apprunner [[YOUR_WEST_SERVICE_ARN="Your us-west-2 App Runner Service ARN"]]
- Go to the bottom of your index.ts file.
Note
The line number might be different, that's ok! Pulumi's code creation can create different code blocks depending on other configs.
- Paste the generated code at the end of the file.
- Delete the duplicate import statements at the top (they're already there from the first import).
Import CloudFront Distribution
Now let's import your CloudFront distribution that routes traffic between regions.
- Go to the CloudFront Console.
- Find your distribution and copy its ID (looks like E1A2B3C4D5E6F7).
Save your CloudFront Distribution ID here: Your CloudFront Distribution ID
- Import the distribution.
pulumi import aws:cloudfront/distribution:Distribution main-cdn [[YOUR_DISTRIBUTION_ID="Your CloudFront Distribution ID"]]
Select the generated TypeScript code from the terminal output and paste it into your index.ts file.
Verify Your Code Matches Infrastructure
After importing all resources, let's verify your TypeScript code accurately represents your deployed infrastructure.
- Run a Pulumi preview.
pulumi preview
What is pulumi preview?
Preview is a dry-run that shows what Pulumi WOULD do if you ran pulumi up. It compares your code to the current state and shows planned changes (create, update, delete). "No changes" means your TypeScript code exactly matches what's deployed - the goal when importing existing infrastructure. Always preview before applying changes!
Nice! You should see a change message in your preview - you've successfully
Add GCP Cloud Run Deployment
You've got AWS locked down - now let's make your app truly unstoppable! In this step, you'll deploy to GCP Cloud Run and bring it under Pulumi's management. By the end, your app will be running on two major cloud providers.
Why multi-cloud?
Think of it like having a backup generator for your house. If the main power grid (AWS) goes down, your backup (GCP) kicks in automatically. Multi-cloud architecture spreads your application across different cloud providers - if AWS experiences a complete regional or global outage, your users won't even notice because GCP keeps serving traffic. The tradeoff is complexity, which is exactly why we're using Pulumi to manage it all in one codebase.
In this step, get ready to:
- Set up GCP credentials and enable required APIs
- Deploy to GCP Cloud Run using source deploy
- Import Cloud Run service into Pulumi
- Update app code to detect GCP environment
Set Up GCP Credentials
First, let's check if you have Google Cloud CLI installed.
- Open Terminal (Mac/Linux) or PowerShell (Windows).
- Run this command:
gcloud version
What do you see?
✅ I see a version number
You already have Google Cloud CLI installed. You're ready to continue!
❌ Command not found
No problem! Let's install Google Cloud CLI.
🍎 macOS
- Open Terminal.
- Install via Homebrew:
brew install google-cloud-sdk
- Verify the installation:
gcloud version
You should see version information for Google Cloud SDK.
🪟 Windows
- Download the installer from cloud.google.com/sdk/docs/install.
- Run the installer and follow the prompts.
- When asked, allow the installer to add gcloud to your PATH.
- Open a new PowerShell window after installation.
- Verify the installation:
gcloud version
You should see version information for Google Cloud SDK.
Still stuck? Ask for help. You can also ask in the NextWork Community.
Authenticate with GCP
- Log in to your GCP account:
gcloud auth login
A browser window will open for authentication.
Grant access to Google Cloud SDK.
Complete the sign-in process.
✅ Authentication succeeded
You're logged in. Continue to set your project.
❌ Browser didn't open or error
If the browser didn't open automatically:
- Copy the URL shown in the terminal.
- Paste it into your browser manually.
- Complete the sign-in and copy the verification code back to the terminal.
If you get a "permission denied" or similar error, make sure you're using a GCP account with project access.
Still stuck? Ask for help. You can also ask in the NextWork Community.
Set Your GCP Project
✅ I have a GCP project
- Go to console.cloud.google.com.
- Click the project dropdown at the top of the page.
- Find your project and copy the Project ID (NOT the project number - the ID looks like my-project-123, while the number is just digits like 123456789).
Save your GCP Project ID here: Your GCP Project ID
- Set your project:
gcloud config set project [[YOUR_GCP_PROJECT_ID="Your GCP Project ID"]]
- Verify it's set:
gcloud config get-value project
❌ I need to create a GCP project
You'll need to create a GCP project first.
- Go to console.cloud.google.com.
- Click the project dropdown at the top → New Project.
- Name it something like multi-cloud-dr.
- Wait for the project to be created.
- Copy the project ID.
Save your GCP Project ID here: Your GCP Project ID
- Set your project:
gcloud config set project [[YOUR_GCP_PROJECT_ID="Your GCP Project ID"]]
Still stuck? Ask for help. You can also ask in the NextWork Community.
❌ I don't have a GCP account
No problem! Let's get you set up with Google Cloud.
Create a GCP Account
- Go to cloud.google.com and click Get started for free.
- Sign in with your Google account (or create one).
- Follow the prompts to set up your GCP account.
Free tier available
GCP offers $300 in free credits for new accounts, plus an always-free tier. Cloud Run is very affordable - you only pay for what you use, and it scales to zero when idle.
Create Your First Project
- After account setup, go to console.cloud.google.com.
- Click the project dropdown at the top → New Project.
- Name it something like multi-cloud-dr.
- Wait for the project to be created.
- Copy the project ID.
Save your GCP Project ID here: Your GCP Project ID
- Set your project:
gcloud config set project [[YOUR_GCP_PROJECT_ID="Your GCP Project ID"]]
Still stuck? Ask for help. You can also ask in the NextWork Community.
Create Application Default Credentials
- Create credentials for Pulumi to use:
gcloud auth application-default login
You will be prompted to select a google account
- Select your Google account.
A browser window will open. Grant the permissions requested.
- Select Continue
Complete the authentication to create local credentials that Pulumi will use.
What are Application Default Credentials?
Extra for Experts: Why two logins?
GCP has two types of credentials:
- (1) gcloud auth login authenticates YOU for running gcloud commands interactively.
- (2) gcloud auth application-default login creates Application Default Credentials (ADC) that tools like Pulumi use to authenticate on your behalf. It's a security best practice - your tools get their own credentials rather than using your personal login.
Enable Required GCP APIs
Cloud Run needs several APIs enabled to function.
- Enable the required APIs.
gcloud services enable run.googleapis.com
gcloud services enable cloudbuild.googleapis.com
gcloud services enable artifactregistry.googleapis.com
What are these APIs?
These three services work together like an assembly line:
- Cloud Build (cloudbuild.googleapis.com) - takes your source code and builds a container image
- Artifact Registry (artifactregistry.googleapis.com) - stores that container image
- Cloud Run (run.googleapis.com) - deploys and runs the container
💡 Extra for Experts: Why do I need to enable APIs manually?
GCP disables most APIs by default for security and billing reasons. Enabling them is a one-time setup - once enabled, they stay on for your project.
Create a .gcloudignore File
Before deploying, let's create a .gcloudignore file to exclude large folders like node_modules and .git from being uploaded. This speeds up deployment significantly.
- In Cursor, right-click in the file explorer and select New File.
- Name the file .gcloudignore.
- Add the following content:
node_modules/
.git/
infrastructure/
- Save the file.
Deploy to GCP Cloud Run
Now let's deploy your application to Cloud Run using source deploy. This builds directly from your source code - no Dockerfile required!
- Navigate back to your application source directory (one level up from infrastructure).
cd ..
- Deploy to Cloud Run with source deploy.
🍎 macOS/Linux
gcloud run deploy multi-region-app \
--source . \
--region us-central1 \
--allow-unauthenticated
🪟 Windows
gcloud run deploy multi-region-app --source . --region us-central1 --allow-unauthenticated
What is "source deploy"?
Source deploy is Cloud Run's magic trick: you give it source code, and it figures out how to containerize it. It uses Google Cloud Buildpacks to detect your language (Node.js, Python, Go, etc.), install dependencies, and build a production container - no Dockerfile needed. The --allow-unauthenticated flag makes the service publicly accessible (like your App Runner services).
Cloud Run will: 1. Detect your application type 2. Build a container using Google Cloud Buildpacks 3. Push to Artifact Registry 4. Deploy to Cloud Run
- Note the service URL from the output (looks like https://multi-region-app-xxxxx-uc.a.run.app).
Save your Cloud Run URL here: Your Cloud Run URL
Deployment taking a long time?
The first deployment is slower because Cloud Build needs to set up the build environment. Subsequent deployments are faster. If it's taking more than 10 minutes, check Cloud Build logs in the GCP Console → Cloud Build → History.
Update App Code for GCP Detection
Your app should display which cloud provider it's running on. Cloud Run sets the K_SERVICE environment variable, while App Runner sets AWS_REGION.
- Open Cursor's chat (Cmd+L or Ctrl+L).
- Send this prompt:
Update index.js to detect which cloud provider is running the app.
The /api/region endpoint should return:
- "Hello from GCP Cloud Run!" when K_SERVICE env var is set
- "Hello from [region]!" when AWS_REGION env var is set (keep current behavior)
- "Hello from local!" when neither is set
Check K_SERVICE first since it's more specific to GCP.
- Review the changes Cursor suggests and click Accept.
Extra for Experts: How does environment detection work?
Each cloud platform sets unique environment variables that identify where your app is running:
- K_SERVICE is set by Cloud Run (Knative under the hood)
- AWS_REGION is set by App Runner
By checking these variables, your app can display different messages or adjust behavior based on which cloud it's running on. This is essential for verifying failover is working correctly.
- Redeploy to Cloud Run with the updated code.
gcloud run deploy multi-region-app --source . --region us-central1 --allow-unauthenticated
- Test the GCP endpoint - open it in your browser:
[[YOUR_CLOUD_RUN_URL="Your Cloud Run URL"]]
✅ Hello from GCP Cloud Run!
Your app is now running on GCP! 🎉
In just a few commands, you've deployed to a completely different cloud provider. This is the power of cloud-native development - your app doesn't care where it runs.
Continue to add the GCP provider to Pulumi.
❌ Wrong response or error
No worries - let's figure this out together!
Still seeing "Hello from AWS!"?
The code update didn't deploy correctly. Redeploy with the updated code:
gcloud run deploy multi-region-app --source . --region us-central1 --allow-unauthenticated
Wait for the deployment to complete, then test again with your Cloud Run URL.
🙋♀️ Getting an error or no response?
- "403 Forbidden": The service might not allow unauthenticated access. Redeploy with --allow-unauthenticated flag. - "Connection refused": The service might still be deploying. Wait a minute and try again. - "Service not found": Check you're using the correct URL from the deployment output.
Still stuck? Ask for help. You can also ask in the NextWork Community.
Add GCP Provider to Pulumi
Now let's add GCP support to your Pulumi project.
- Navigate back to your infrastructure directory.
cd infrastructure
- Install the GCP Pulumi provider.
npm install @pulumi/gcp
- Add GCP configuration to your Pulumi stack.
pulumi config set gcp:project [[YOUR_GCP_PROJECT_ID="Your GCP Project ID"]]
pulumi config set gcp:region us-central1
Import Cloud Run Service into Pulumi
Import your deployed Cloud Run service so Pulumi can manage it.
- Get your Cloud Run service details.
gcloud run services describe multi-region-app --region us-central1 --format='value(name)'
- Import the service into Pulumi.
pulumi import gcp:cloudrun/service:Service gcp-cloudrun locations/us-central1/namespaces/[[YOUR_GCP_PROJECT_ID="Your GCP Project ID"]]/services/multi-region-app
- Select the generated TypeScript code from the terminal output and paste it into your index.ts file.
- Remove the repeated pulumi import.
Verify GCP Import
Run preview to confirm the GCP resource is properly imported.
pulumi preview
You should see your GCP Cloud Run service listed with no changes.
Boom! 💥 Your Pulumi code now manages infrastructure across two cloud providers - AWS and GCP. This is real multi-cloud IaC in action. One codebase, multiple clouds.
Configure CloudFront for Multi-Cloud Failover
Time to see your multi-cloud setup in action! You've got AWS and GCP running - now let's connect them with CloudFront so traffic automatically fails over when something goes wrong. By the end of this step, you'll pause AWS and watch your app seamlessly switch to GCP. This is disaster recovery in action!
Why CloudFront for multi-cloud failover?
CloudFront's origin groups support automatic failover between origins. When the primary origin returns an error (5xx or 4xx), CloudFront automatically routes the request to the secondary origin. By making GCP your secondary origin, you get automatic multi-cloud failover without needing a custom domain or Route 53 setup.
In this step, get ready to:
- Replace us-west-2 origin with GCP Cloud Run in CloudFront
- Update the origin group for AWS → GCP failover
- Import updated CloudFront configuration into Pulumi
- Test failover by pausing AWS App Runner
Update CloudFront Origins
First, let's add GCP Cloud Run as a new origin in your CloudFront distribution.
- Go to the CloudFront Console.
- Click on your distribution to open it.
- Go to the Origins tab.
Add GCP Cloud Run Origin
- Click Create origin.
- For Origin domain, enter your GCP Cloud Run URL (without https://): Your Cloud Run URL
- For Name, enter gcp-cloud-run.
- For Protocol, select HTTPS only.
- Click Create origin.
Cross-cloud origins work seamlessly
CloudFront doesn't care where your origin is hosted - it just needs a valid HTTPS endpoint. Your GCP Cloud Run URL works exactly like any other origin. This is the beauty of using a CDN for multi-cloud architecture.
Update Origin Group
Now update your existing origin group to use GCP as the secondary instead of us-west-2.
- Stay on the Origins tab.
- Find your origin group (named failover-group or similar) and click on it.
- Click Edit.
- Remove the us-west-2 origin from the group.
- Add your new gcp-cloud-run origin as the secondary.
- Make sure the order is:
- 1 (Primary): us-east-1 App Runner origin
- 2 (Secondary): gcp-cloud-run origin
- Verify Failover criteria includes: 500, 502, 503, 504.
- Click Save changes.
Origin group failover criteria
CloudFront monitors responses from your primary origin. When it receives any of the configured error codes (5xx errors), it automatically retries the request against your secondary origin. This happens transparently to users - they just see a working response, even if the primary is down.
Wait for CloudFront Deployment
- Go back to the General tab.
- Wait for Last modified status to show the distribution is deployed (2-5 minutes).
Verify Pulumi State
Let's make sure Pulumi's state is in sync with your CloudFront changes.
- Navigate to your infrastructure directory.
cd infrastructure
- Refresh Pulumi's state to pick up the console changes.
pulumi refresh
- Confirm the refresh when prompted.
This updates Pulumi's state to match the actual cloud resources. You should see your CloudFront distribution with the new GCP origin.
- Run a preview to verify everything is in sync.
pulumi preview
You should see "X unchanged" with no pending changes - this means Pulumi's state matches the deployed configuration.
Test Failover
Now let's verify the multi-cloud failover works correctly!
Testing the API endpoint
We'll test the /api/region endpoint, which returns a simple text response showing which cloud is serving traffic. The root URL (/) serves a latency test page that makes direct calls to App Runner - that won't show CloudFront failover behavior.
Test 1: All services healthy
Open your browser and go to your CloudFront API endpoint:
https://[[YOUR_CLOUDFRONT_URL="Your CloudFront URL (e.g. d1234abcd5678.cloudfront.net)"]]/api/region
✅ Hello from us-east-1!
Your primary (us-east-1) App Runner service is responding through CloudFront. Continue to Test 2.
❌ Error or GCP response
Let's troubleshoot together!
Seeing "Hello from GCP Cloud Run!" instead?
Your AWS primary might be unhealthy or paused. Go to the App Runner Console in us-east-1 and check your service shows Running. If paused, click Actions → Resume and wait 2-3 minutes.
🙋♀️ Getting an error?
- Verify your CloudFront distribution is Enabled (not Deploying). - Check the origin group has the correct origins in the right order. - Try opening in an incognito window to bypass caching.
Still stuck? Ask for help. You can also ask in the NextWork Community.
Test 2: Pause AWS to trigger failover
- Go to the App Runner Console in us-east-1.
- Click on your service.
- Click Actions → Pause.
- Confirm the pause action.
- Wait 1-2 minutes for the service to fully pause.
- Refresh your browser at your CloudFront API endpoint:
https://[[YOUR_CLOUDFRONT_URL="Your CloudFront URL (e.g. d1234abcd5678.cloudfront.net)"]]/api/region
✅ Hello from GCP Cloud Run!
Multi-cloud failover is working! When AWS App Runner went down, CloudFront automatically routed traffic to GCP Cloud Run.
This is production-grade DR
What you just built is exactly how companies survive cloud outages. Your application now has two independent failure domains: AWS and GCP. If AWS experiences a regional or even global outage, your users are automatically served by GCP. This is the power of multi-cloud architecture - and you're managing it all with Pulumi!
❌ Error or still showing us-east-1
Almost there! Let's figure this out.
Still seeing "Hello from us-east-1!"?
The service might not be fully paused yet. Go to the App Runner Console and verify the service shows Paused status. Try opening in an incognito/private window to bypass CloudFront edge caching. Wait another minute and try again.
🙋♀️ Getting an error?
- Verify your GCP Cloud Run service is running: gcloud run services describe multi-region-app --region us-central1 - Check the origin group has gcp-cloud-run as the secondary origin. - Verify the failover criteria includes 5xx error codes.
Still stuck? Ask for help. You can also ask in the NextWork Community.
Restore AWS Service
- Go to the App Runner Console in us-east-1.
- Click on your service → Actions → Resume.
- Wait 2-3 minutes for the service to start.
- Refresh your browser at the /api/region endpoint - traffic should return to AWS showing "Hello from us-east-1!".
Automatic failback
Once your primary origin is healthy again, CloudFront automatically routes new requests back to it. No manual intervention needed - this is the power of automated disaster recovery!
Secret mission
Your failover works automatically - but how do you know what's happening across your infrastructure?
By now, you've built multi-cloud failover that routes traffic between AWS and GCP. But to truly manage a production system, you need visibility. In this secret mission, you'll use Cursor + Pulumi to create a CloudWatch dashboard that shows your entire DR setup at a glance.
Build a Multi-Cloud Monitoring Dashboard
Clean Up Your Resources
Clean Up Your Resources
Congratulations on completing this project! Your multi-cloud disaster recovery setup is yours to keep, and you can build on it in future projects. However, if you'd like to clean up to save costs, here are your options.
Optional resources to manage:
- AWS App Runner (us-east-1): Can be paused to stop charges, or deleted entirely
- GCP Cloud Run: Scales to zero automatically - minimal cost if unused
- CloudFront distribution: Pay-per-request only - can be deleted if not needed
- Pulumi stack: Local state can be removed after deleting cloud resources
✅ Keep or pause resources
If you want to keep everything running:
Your infrastructure is ready to use! Here are the ongoing costs to be aware of:
- CloudFront: Pay-per-request (likely <$1/month for low traffic)
- AWS App Runner: ~$5/month minimum per service
- GCP Cloud Run: Pay-per-request (likely <$1/month for low traffic)
- Total: ~$6-8/month for the full setup
If you want to pause to save money:
Pause AWS App Runner service (stops compute charges):
- Go to the App Runner Console in us-east-1.
- Click on your service → Actions → Pause.
Cloud Run already scales to zero automatically - no action needed.
CloudFront has no minimum charges - you only pay for requests.
To resume later:
- Go to the App Runner Console in us-east-1.
- Click on your service → Actions → Resume.
❌ Delete everything
If you're done with the project and want to clean up completely:
1. Delete CloudFront distribution:
- Go to the CloudFront Console.
- Select your distribution → Disable.
- Wait for the distribution to be disabled (2-5 minutes).
- Select the distribution again → Delete.
2. Delete GCP Cloud Run service:
- Go to the Cloud Run Console.
- Select your multi-region-app service.
- Click Delete → confirm.
3. Delete AWS App Runner service:
- Go to the App Runner Console in us-east-1.
- Select your service → Actions → Delete.
4. Delete Pulumi stack:
pulumi destroy --yes # Removes any resources Pulumi created
pulumi stack rm dev --yes # Removes the stack
5. (Optional) Delete local project folder:
Delete the infrastructure folder from your multi-region-app project.
Verify cleanup:
- CloudFront distribution deleted
- GCP Cloud Run service deleted
- AWS App Runner service deleted
- Pulumi stack removed
Still stuck? Ask for help. You can also ask in the NextWork Community.
That's a wrap!
That's a wrap!
You did it! You've built a multi-cloud disaster recovery setup managed entirely with Infrastructure as Code.
You've learned how to:
- 🛠️ Import existing AWS infrastructure into Pulumi and manage it as TypeScript code.
- ☁️ Deploy your application to GCP Cloud Run for true multi-cloud redundancy.
- 🌐 Configure CloudFront origin groups for automatic AWS → GCP failover.
- 💎 Use Cursor + Pulumi to build a CloudWatch dashboard for real-time monitoring.
Ready to quiz yourself on the main concepts in this project?!
p.s. Does it say "Still tasks to complete!" at the bottom of the screen?
This means you still have screenshots left to upload, or questions left to answer!
- Press Ctrl+F (Windows) or Command+F (Mac) on your keyboard.
- Search for the text Return to later.
- Jump straight to your incomplete tasks!
- 🙋♀️ Still stuck? Ask the community!