Building and Deploying a Portfolio Website on Azure Static Web Apps with Terraform and GitHub Actions

How I deployed my portfolio to Azure Static Web Apps using Terraform, set up a CI/CD pipeline with GitHub Actions, and generated a clean frontend with Lovable.dev.

I wanted to find a way to practice my Azure and Terraform skills. What better way than to build a production-ready website and finally put my theoretical knowledge into action?

My main goal was to learn how to use Terraform to deploy resources to Azure. However, along the way, I picked up more useful tools and skills than I expected — from generating frontends with AI to setting up a basic CI/CD pipeline using GitHub Actions.

By the end of this blog, you’ll learn how I:

In my next blog post, I’ll walk through how I integrated Azure Functions into my site to track visitor counts, handle a contact form via the Mailersend API, and set up real-time notifications for new visitor IPs using the ipinfo API.


Generating a Frontend with Lovable.dev

I wanted to make sure my portfolio website was running locally before deploying anything to Azure. I had heard about a few AI-based website generators, and after trying a couple, I found that lovable.dev gave me the cleanest UI out of the box.

I signed up for the free version and started working on my first prompt.

Pro Tip: Lovable.dev only gives you 5 prompts per day — so make them count. I didn’t realize this at first and burned through them quickly. I had to work with the result I got, which meant I spent quite a bit of time later tweaking the frontend to fit my needs 😅.

Instead of deep diving into prompt engineering guides, I just opened Notepad and wrote down what I wanted my website to look like. Then, I asked ChatGPT to turn that into a proper prompt for Lovable.

Here’s an example of the prompt it helped me create:

“Help me create a professional and sleek portfolio web app. I will attach my resume for reference to build the content around my experience and skills. The overall design should reflect a modern vibe, but still remain clean, simple, and visually attractive.”

Sections & Features I Asked For

Once you’re happy with the results on Lovable, click the GitHub icon at the top right, choose “Connect GitHub,” and follow the steps. This will create a new GitHub repo and push the full codebase there.

After that, clone the repo to your local machine using VS Code and start fine-tuning the website to fit your exact needs.


Setting Up the Local Dev Environment in VS Code

The code generated by Lovable was written in React, so before making any changes, I had to:

  1. Install Node.js and npm
  2. Set up the local development environment using Vite (as the build tool and dev server)
  3. Run the project locally using npm run dev

Once I had the site running locally, I started cleaning up unnecessary Lovable artifacts:

Pro Tip: Track all your changes with Git! It saved me more than once when Copilot made suggestions that seemed right but didn’t work as expected.

After I was satisfied with the website locally, I also asked Copilot to do a quick pass through the code and flag any potential security issues — especially important since I planned to integrate APIs and backend services later.


Deploying to Azure with Terraform

Once the frontend was ready, I moved on to writing Terraform code to deploy it to Azure Static Web Apps.

Prerequisites

Log in to your Azure subscription:

az login

Terraform Configuration

main.tf

resource "azurerm_resource_group" "rg" {
  name     = "rg-${var.application_name}-${var.environment_name}"
  location = var.primary_location
}

resource "azurerm_static_web_app" "webapp" {
  name                = "WebApp-${var.application_name}-${var.environment_name}"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  repository_url      = "your github url"
  repository_branch   = "main"
  repository_token    = var.github_token
}

versions.tf

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.30.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.5.1"
    }
  }
}

provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
}

variables.tf

variable "application_name" {
  type = string
}

variable "environment_name" {
  type = string
}

variable "primary_location" {
  type = string
}

variable "github_token" {
  type        = string
  description = "GitHub Personal Access Token for repository access"
}

variable "subscription_id" {
  type = string
}

variable "tenant_id" {
  type        = string
  description = "Azure Tenant ID"
}

terraform.tfvars (never commit this to version control)

application_name = "portfolio"
environment_name = "Prod"
primary_location = "centralus"
github_token     = "github_yourtoken"
subscription_id  = "yoursubid"
tenant_id        = "yoursubtenantid"

Note: It is strongly recommended to use environment variables or a secure vault (e.g., Azure Key Vault, HashiCorp Vault) for sensitive values like tokens and IDs in production. Never hardcode them or check them into version control.

Running Terraform

terraform init
terraform plan
terraform apply

This will create your Azure Static Web App. You can verify it’s live in the Azure portal at portal.azure.com. You’ll receive a fully qualified domain name (FQDN) to access the site.


Setting Up GitHub Actions CI/CD

After deploying with Terraform, my Static Web App didn’t automatically create a GitHub Actions workflow — likely due to a missing or invalid repository_token. To fix this, I manually ran the following command to link the GitHub repo and generate the workflow file:

az staticwebapp update \
  --name WebApp-portfolio-Prod \
  --branch main \
  --source https://github.com/yourpath-to/repo \
  --token ghh-yourtoken \
  --output jsonc

This created a YAML file in your repository under .github/workflows/. This file defines your CI/CD pipeline and is used by GitHub Actions to automatically build and deploy your site.

One Gotcha with Vite

I hit an issue where GitHub Actions couldn’t find my built output because I was using Vite. The fix was simple — set the app_artifact_location in the YAML:

app_artifact_location: dist

After adding that, the pipeline ran successfully and my website went live. From that point on, every commit to the main branch automatically triggers a build and deploys the updated code to Azure Static Web Apps.


That’s the full setup. With Terraform handling the infrastructure and GitHub Actions handling deployments, I now have a production-ready portfolio with a proper CI/CD pipeline — all built as a hands-on learning project.

Need help implementing this?

If you're dealing with something similar and want hands-on help, I'm available for consulting engagements.

Get in touch →