{"id":74125,"date":"2018-11-16T13:18:59","date_gmt":"2018-11-16T21:18:59","guid":{"rendered":""},"modified":"2025-06-30T00:58:36","modified_gmt":"2025-06-30T07:58:36","slug":"terraform-jamstack-azure-gatsby-azure-pipelines-git","status":"publish","type":"post","link":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/","title":{"rendered":"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git"},"content":{"rendered":"\n<p>This article will show how to build a blog (or any other static content) using a very popular&nbsp;<a href=\"https:\/\/jamstack.org\/\">JAMstack<\/a>&nbsp;(GatsbyJS, GraphQL, Markdown) and host it on&nbsp;<a href=\"https:\/\/azure.microsoft.com\/en-ca\/blog\/azure-storage-static-web-hosting-public-preview\/\">static website hosting for Azure Storage<\/a>, which provides a cost effective and scalable solution for hosting static content and JavaScript code.<\/p>\n\n\n\n<p>We will demonstrate how to optimize the website and provide global reach by enabling Azure CDN. And, to make all the blog posts continuously build and deploy to Azure, we will utilize&nbsp;<a href=\"https:\/\/azure.microsoft.com\/en-ca\/services\/devops\/pipelines\/\">Azure Pipelines.<\/a>&nbsp;Then, we will provision Azure resources using the simple and powerful language of &nbsp;<a href=\"https:\/\/www.terraform.io\/\">Terraform<\/a>&nbsp;and a Docker container running inside the pipeline. &nbsp;The following diagram shows the workflow described in the article:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-workflow_image-1-v2.png\" alt=\"JAMStack - diagram 1\" class=\"wp-image-74148\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"what-is-jamstack\">What is JAMstack<\/h3>\n\n\n\n<p>Static site generators have been around for a while \u2014 tools like Jekyll and Hugo are widely adopted. Recently the notion of&nbsp;<a href=\"https:\/\/jamstack.org\/\">JAMstack<\/a>&nbsp;is making a buzz. It is a modern web development architecture based on client-side JavaScript, reusable APIs, and prebuilt Markup. The main difference with traditional CMS based systems (like Drupal or WordPress) is that there is no backend logic or persistence layer access \u2014 everything is encapsulated in the API.<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-1024x802.webp\" alt=\"JAMStack - diagram 2\" class=\"wp-image-74683 webp-format\" srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-1024x802.webp 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-300x235.webp 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-768x602.webp 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-1536x1203.webp 1536w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-319x250.webp 319w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-330x258.webp 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-800x627.webp 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-400x313.webp 400w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1.webp 2007w\" data-orig-src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAMStack-diagram_image2-1-1024x802.webp\"><\/figure>\n\n\n\n<p id=\"caption-attachment-74144\"><em>Image from jamstack.org<\/em><\/p>\n\n\n\n<p>The key benefits are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Faster to load \u2013 just static files, no server side<\/li>\n\n\n\n<li>Cheaper \u2013 no servers to maintain<\/li>\n\n\n\n<li>Easier to scale \u2013 serve files in more places like CDN<\/li>\n\n\n\n<li>Developer experience \u2013 easier to develop<\/li>\n\n\n\n<li>Security \u2013 best practices encapsulated in API<\/li>\n<\/ul>\n\n\n\n<p>You can see a lot of JAMstack examples listed on&nbsp;<a href=\"https:\/\/jamstack.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">their site<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"why-gatsbyjs\">Why GatsbyJS<\/h3>\n\n\n\n<p>GatsbyJS is a new player in JAMstack and is quickly gaining popularity. At the time of this writing, it is in the fourth place according to&nbsp;<a href=\"https:\/\/staticgen.com\/\">https:\/\/staticgen.com.<\/a><\/p>\n\n\n\n<p>What makes GatsbyJS especially attractive is the power of the latest web technologies \u2013 React.js, Webpack, modern JavaScript, and GraphQL API.<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-1024x911.webp\" alt=\"What is Gatsby - diagram 3\" class=\"wp-image-74686 webp-format\" srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-1024x911.webp 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-300x267.webp 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-768x683.webp 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-1536x1367.webp 1536w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-281x250.webp 281w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-330x294.webp 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-800x712.webp 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-400x356.webp 400w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3.webp 1626w\" data-orig-src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/What-is-Gatsby_image-3-1024x911.webp\"><\/figure>\n\n\n\n<p id=\"caption-attachment-74137\"><em>Image from gatsbyjs.org<\/em><\/p>\n\n\n\n<p>The process for building the site in Gatsby could be summarized as:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Defining data sources (GitHub markdown in our example)<\/li>\n\n\n\n<li>Querying data with GraphQL and making it available to the page<\/li>\n\n\n\n<li>Rendering data using React components<\/li>\n\n\n\n<li>Generating static pages and hosting it (Azure Static site in our example)<\/li>\n<\/ul>\n\n\n\n<p>Among the main benefits of GatsbyJS is the usage of familiar frontend technologies such as React and Webpack, extensibility \u2013 it has tons of plugins (PWA, SEO, etc.), optimized performance, and build tooling. The only downside I noticed is that for now it does not have as many themes built by communities as for Hugo or Jekyll, but it is already growing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"creating-a-gatsby-site\">Creating a Gatsby site<\/h3>\n\n\n\n<p>For my blog I have chosen clean and simple GatsbyJS minimal blog starter:&nbsp;<a href=\"https:\/\/github.com\/LekoArts\/gatsby-starter-minimal-blog\">https:\/\/github.com\/LekoArts\/gatsby-starter-minimal-blog<\/a>. It has all the basic features and plugins already integrated \u2013 Markdown, Styled components, offline support, and SEO. To start, the site based on this template, install Gatsby CLI and generate the site (the only prerequisite is node.js):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nnpm install --global gatsby-cli\ngatsby new gatsby-site https:\/\/github.com\/LekoArts\/gatsby-starter-minimal-blog\n<\/pre><\/div>\n\n\n<p>To start the generated site in develop mode that supports hot reload:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n>cd gatsby-site\n>gatsby develop\n\nsuccess update schema \u2014 0.623 s\nsuccess extract queries from components \u2014 0.734 s\nsuccess run graphql queries \u2014 0.208 s \u2014 4\/4 20.57 queries\/second\nsuccess write out page data \u2014 0.150 s\nsuccess write out redirect data \u2014 0.006 s\n\u2840 onPostBootstrapdone generating icons for manifest\nsuccess onPostBootstrap \u2014 0.501 s\ninfo bootstrap finished - 157.202 s\n DONE  Compiled successfully in 20983ms                                                                                                 \n\nYou can now view gatsby-starter-minimal-blog in the browser.\n\n  http:\/\/localhost:8000\/\n\nView GraphiQL, an in-browser IDE, to explore your site's data and schema\n\n  http:\/\/localhost:8000\/___graphql\n\nNote that the development build is not optimized.\nTo create a production build, use gatsby build\n<\/pre><\/div>\n\n\n<p>Navigate to the site or graphql endpoint to learn the query language. Now we will customize the site to have nice Azure blue colors.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Azure-blue-filters_image4.png\" alt=\"VS Code - Screenshot\" class=\"wp-image-74139\" \/><\/figure>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Modify global site settings in config\/SiteConfig.js<\/li>\n\n\n\n<li>Add blog posts in blog<\/li>\n\n\n\n<li>Update theme style in config\/Theme.js<\/li>\n\n\n\n<li>Go over plugins in gatsby-config.js<\/li>\n\n\n\n<li>Adjust styles in components\/Layout.js<\/li>\n<\/ul>\n\n\n\n<p>As we are going through the changes, they are immediately compiled and the page is reloaded, making the developer feedback loop very fast. Below is the result the changes, and the site is added to&nbsp;<a href=\"https:\/\/github.com\/lenisha\/great-gasby-blog\">GitHub here<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/JAM-is-fun_image5.png\" alt=\"Latest posts - JAMStack screenshot\" class=\"wp-image-74130\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"setting-up-ci-cd-with-azure-pipelines\">Setting up CI\/CD with Azure Pipelines<\/h3>\n\n\n\n<p>To host the site on Azure, one of the best options is to use Azure Static Site for Storage and enable Azure CDN for global presence. The steps we need to automate are:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Generate static files from Gatsby react.js components using `gatsby build`<\/li>\n\n\n\n<li>Provision the Azure Storage account<\/li>\n\n\n\n<li>Enable Static Website option on storage account (See\u00a0<a href=\"https:\/\/azure.microsoft.com\/en-ca\/blog\/azure-storage-static-web-hosting-public-preview\/\">Microsoft blog<\/a>\u00a0for details)<\/li>\n\n\n\n<li>Provision Azure CDN profile and endpoint with origin pointing to Storage account web URL (<a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/storage\/blobs\/storage-https-custom-domain-cdn\">CDN for blob<\/a>)<\/li>\n\n\n\n<li>Upload static assets to $web container in storage account<\/li>\n<\/ul>\n\n\n\n<p>Following best DevOps practices, we will separate Build and Deployment phases into two pipelines \u2013 one is responsible for building the site, and another for provisioning Azure resources and deploying the files.<\/p>\n\n\n\n<p>Enabling Azure Pipelines is a process of just a few clicks as integration is now available in GitHub marketplace.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Azure-Pipelines_image6.png\" alt=\"GitHub - screenshot 1\" class=\"wp-image-74140\" \/><\/figure>\n\n\n\n<p>Search `Azure` in the Marketplace and click it and Authorize Azure Pipelines to access the GitHub repository with the blog. You\u2019ll be prompted to create a DevOps organization and project:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Pipeline-project_image-7.png\" alt=\"GitHub - screenshot 2\" class=\"wp-image-74134\" \/><\/figure>\n\n\n\n<p>It will analyze the repo and suggest the build template based on the content. In our case it\u2019s Node.js with React.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/new-pipeline_image8.png\" alt=\"GitHub pipeline - screenshot 1\" class=\"wp-image-74133\" \/><\/figure>\n\n\n\n<p>The resulting YAML pipeline `azure-pipelines.yml` will be added to the repository. We will add few more tweaks to install and run Gatsby CLI and publish the resulting build artifact:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\npool:\n  vmImage: 'Ubuntu 16.04'\n\ntrigger:\n  branches:\n    include:\n    - master\n\nsteps:\n- task: NodeTool@0\n  inputs:\n    versionSpec: '8.x'\n  displayName: 'Install Node.js'\n\n- script: |\n    npm install -g gatsby-cli\n  displayName: 'install gatsby'\n\n- script: |\n    cd gatsby-site\n    npm install\n    npx gatsby build\n  displayName: 'gatsby build'\n \n\n- task: CopyFiles@2\n  inputs:\n    sourceFolder: 'gatsby-site\/public'\n    contents: '**' \n    targetFolder: $(Build.ArtifactStagingDirectory)\n    cleanTargetFolder: true \n  displayName: 'copy built site'\n\n- task: PublishBuildArtifacts@1\n  inputs:\n    pathtoPublish: '$(Build.ArtifactStagingDirectory)' \n    artifactName: 'drop' \n    publishLocation: 'Container'\n<\/pre><\/div>\n\n\n<p>Azure Pipelines comes with a lot of&nbsp;<a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/devops\/pipelines\/tasks\/?view=vsts\">prebuilt tasks and good documentation<\/a>&nbsp;available for copy\/paste into YAML files.<\/p>\n\n\n\n<p>Our pipeline uses `NodeTool` task that installs the required version of node.js, and enables us to run the steps to install Gatsby and generate the Gatsby site. We also use `Copy Files` and `PublishBuildArtifacts` to make the artifacts available to our deployment pipeline.&nbsp; By default, CI and Pull Request validation is enabled and as we push code to git, pipeline invoked and builds the site:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Terraform-scripts_image9.png\" alt=\"GitHub pipeline - screenshot 2\" class=\"wp-image-74136\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"continuous-deployment-setup\">Continuous Deployment Setup<\/h3>\n\n\n\n<p>Although Azure Pipelines have a Release pipelines construct, it does not yet support YAML. So, we decided to follow the \u201cPipeline as Code\u201d principle and create the deployment pipeline using the same Azure build pipeline syntax.<\/p>\n\n\n\n<p>The idea is that the Deployment pipeline will be triggered only if Build has completed or if files related to infrastructure have changed. All the deployment files and pipeline are placed in the repo in the `deploy` folder.<\/p>\n\n\n\n<p>We have created one more Build pipeline and pointed it to `deploy\/azure-pipelines-deploy.yml`.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deploy_image-10.png\" alt=\"Continuous Deployment Setup - screenshot 1\" class=\"wp-image-74142\" \/><\/figure>\n\n\n\n<p>To make sure Deployment is invoked after the build \u2013 use a combination of Triggers (<a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/devops\/pipelines\/build\/triggers?view=vsts&amp;tabs=designer\">build completion is not yet supported<\/a>&nbsp;in YAML so we do it in designer) \u2013 Continuous integration includes only \u201cdeploy\/*\u201d path, and Build Completion is linked to our build pipeline.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/CI_image-11.png\" alt=\"Continuous Deployment Setup - screenshot 2\" class=\"wp-image-74127\" \/><\/figure>\n\n\n\n<p>Now to make sure Build is only triggered when the website content is changed and not deployment artifacts, add a Trigger that excludes \u201cdeploy\/*\u201d from the continuous integration in our Build pipeline and includes master branch (we will see later how to build and validate code in branches):<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-1024x568.webp\" alt=\"Continuous Deployment Setup - screenshot 3\" class=\"wp-image-74713 webp-format\" srcset=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-1024x568.webp 1024w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-300x166.webp 300w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-768x426.webp 768w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-1536x851.webp 1536w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-2048x1135.webp 2048w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-330x183.webp 330w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-800x443.webp 800w, https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-400x222.webp 400w\" data-orig-src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deployed-updated-image-1024x568.webp\"><\/figure>\n\n\n\n<p>As a result, we have two phased Build and Deployment processes where any change to content triggers Build and subsequently the Deployment pipeline, and any change to deployment assets Triggers only the Deployment pipeline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"terraform-azure-static-website-and-cdn\">Terraform Azure Static Website and CDN<\/h3>\n\n\n\n<p>Provisioning resources with Terraform HCL is pretty straightforward \u2013 setup the storage account that will store the Terraform state and point to it in `backend.tfvars`, copy and paste the Azure Storage account and CDN resource definitions from the Terraform&nbsp;<a href=\"https:\/\/www.terraform.io\/docs\/providers\/azurerm\/index.html\">AzureRM provider website<\/a>. The full code is available in&nbsp;<a href=\"https:\/\/github.com\/lenisha\/great-gasby-blog\/tree\/master\/deploy\/terraform\">GitHub<\/a>.<\/p>\n\n\n\n<p>Note that there are a couple of tweaks we had to do to enable the Static Website feature as it is currently only supported though Azure CLI or API. (No ARM or Terraform support yet). To do that there is a `local-exec` provisioner provided by Terraform to run the scripts. Here is the snippet that runs the Azure CLI command to enable the static website for storage:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nresource \"azurerm_storage_account\" \"webblob\" {\n  name                     = \"${var.dns_name}\"\n  location                 = \"${azurerm_resource_group.demo-rg.location}\"\n  resource_group_name      = \"${azurerm_resource_group.demo-rg.name}\"\n  account_kind             = \"StorageV2\"\n  account_tier             = \"Standard\"\n  account_replication_type = \"LRS\"\n\n   provisioner \"local-exec\" {\n    command = \"az storage blob service-properties update --account-name ${azurerm_storage_account.webblob.name} --static-website  --index-document index.html --404-document 404.html\"\n  }\n}\n<\/pre><\/div>\n\n\n<p>Once the static website is enabled it provides the read only endpoint URL to access the website. We need to get this URL as it will be used by Azure CDN as origin. It\u2019s a complicated task as this feature is not managed by Terraform, and it\u2019s hard to get output from commands to variables.<\/p>\n\n\n\n<p>Fortunately, there is a great Terraform Shell Resource Module available that helps to manage the output of shell commands&nbsp;<a href=\"https:\/\/registry.terraform.io\/modules\/matti\/resource\/shell\/0.3.1\">https:\/\/registry.terraform.io\/modules\/matti\/resource\/shell\/0.3.1<\/a>. Here is the code that queries the Storage account to get the endpoint:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nmodule \"query_url\" {\n  source  = \"matti\/resource\/shell\"\n\n  command = \"printf $(az storage account show -n ${azurerm_storage_account.webblob.name} -g ${azurerm_resource_group.demo-rg.name} --query \\\"primaryEndpoints.web\\\" --output tsv | cut -d \\\"\/\\\" -f 3)\"\n}\n<\/pre><\/div>\n\n\n<p>Now we can provision Azure CDN and CDN Endpoint pointing to our Static Website:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n# Create Azure CDN profile\nresource \"azurerm_cdn_profile\" \"webblob-cdn\" {\n  name                = \"${azurerm_storage_account.webblob.name}cdnprofile\"\n  location            = \"${azurerm_resource_group.demo-rg.location}\"\n  resource_group_name = \"${azurerm_resource_group.demo-rg.name}\"\n  sku                 = \"Standard_Verizon\"\n}\n\n# Point Azure CDN profile to web endpoint for Static website\nresource \"azurerm_cdn_endpoint\" \"webblob-cdn-endpt\" {\n  name                = \"${var.dns_name}\"\n  profile_name        = \"${azurerm_cdn_profile.webblob-cdn.name}\"\n  location            = \"${azurerm_resource_group.demo-rg.location}\"\n  resource_group_name = \"${azurerm_resource_group.demo-rg.name}\"\n  is_http_allowed     = \"false\"\n  optimization_type   = \"GeneralWebDelivery\"\n  origin_host_header  = \"${module.query_url.stdout}\"\n  querystring_caching_behaviour = \"IgnoreQueryString\"\n  \n  origin {\n    name      = \"assets\"\n    host_name = \"${module.query_url.stdout}\"\n    https_port = \"443\"\n  }\n  depends_on = [\"module.query_url\"]\n}\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"terraform-deployment-pipeline\">Terraform Deployment Pipeline<\/h3>\n\n\n\n<p>Coming back to automation, our deployment pipeline will have to download Build artifacts from the successful Build and invoke Terraform to provision infrastructure, then upload site artifacts to the storage account.<\/p>\n\n\n\n<p>Azure Pipelines has a very powerful ability to run the tasks in a container that could be downloaded from DockerHub or ACR. To enable our pipeline I have built a small lightweight container based on Ubuntu 16.04 with just Terraform and Azure CLI tools here:&nbsp;<a href=\"https:\/\/hub.docker.com\/r\/lenisha\/terraform-light\/\">https:\/\/hub.docker.com\/r\/lenisha\/terraform-light\/<\/a>. It is used in our deployment pipeline below:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\nresources:\n  containers:\n  - container: terraform_container\n    image: lenisha\/terraform-light\n\npool:\n  vmImage: 'Ubuntu 16.04'\n\ncontainer: terraform_container\n<\/pre><\/div>\n\n\n<p>Once container is downloaded and run by the pipeline, we add steps to run the Azure CLI Login (required for Terraform to be able to run our az cli commands) and to deploy the Terraform scripts:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n- script: |\n    az extension add --name storage-preview\n    az login --service-principal -u $(ARM_CLIENT_ID) -p $(ARM_CLIENT_SECRET) --tenant $(ARM_TENANT_ID)\n  displayName: 'Enable AZ extension and Login'\n\n- script: |\n    cd deploy\/terraform\n    terraform init -backend-config=backend.tfvars\n    terraform apply -auto-approve -var dns_name=$(BLOB_NAME)\n  displayName: 'Terraform init and apply'\n  env:\n      ARM_TENANT_ID: $(ARM_TENANT_ID)\n      ARM_SUBSCRIPTION_ID: $(ARM_SUBSCRIPTION_ID)\n      ARM_CLIENT_ID: $(ARM_CLIENT_ID)\n      ARM_CLIENT_SECRET: $(ARM_CLIENT_SECRET)\n      ARM_ACCESS_KEY: $(ARM_ACCESS_KEY)\n<\/pre><\/div>\n\n\n<p>To connect Terraform to a subscription and backend storage for state,&nbsp;<a href=\"https:\/\/docs.microsoft.com\/en-us\/cli\/azure\/create-an-azure-service-principal-azure-cli?view=azure-cli-latest\">create Service Principal<\/a>&nbsp;and add Pipeline variables for the Azure subscription and Service Principal:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ARM_SUBSCRIPTION_ID, ARM_TENANT_ID : Subscription (run az account list in Azure CLI to find out tenantId and subscriptionId )<\/li>\n\n\n\n<li>ARM_CLIENT_ID, ARM_CLIENT_SECRET: Service Principal is and password obtained when creating SP<\/li>\n\n\n\n<li>ARM_ACCESS_KEY: Backend State Storage account access key<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Blog-deploy_image-14.png\" alt=\"Continuous Deployment Setup - screenshot 4\" class=\"wp-image-74126\" \/><\/figure>\n\n\n\n<p>In addition, define BLOB_NAME variable \u2013 it will be the name of the blob site and it is used in the URL constructed for the blog: https:\/\/BLOB_NAME.azureedge.net<\/p>\n\n\n\n<p>As a next step, add tasks to Download build artifacts, and upload the static artifacts to the storage:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n- task: DownloadBuildArtifacts@0\n  inputs:\n    buildType: 'specific' # Options: current, specific\n    project: 'gatsby-blog' # Required when buildType == Specific\n    pipeline: 'great-gasby-blog' # Required when buildType == Specific\n    downloadType: 'single' # Options: single, specific\n    artifactName: 'drop' # Required when downloadType == Single\n    downloadPath: '.' \n\n- script: |\n    az storage blob upload-batch -s drop -d \\$web --account-name $(BLOB_NAME)\n  displayName: 'copy content and site is ready https:\/\/$(BLOB_NAME).azureedge.net\/index.html'\n<\/pre><\/div>\n\n\n<p>The resulting job view:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/Add-badges_image-15.png\" alt=\"Continuous Deployment Setup - screenshot 5\" class=\"wp-image-74138\" \/><\/figure>\n\n\n\n<p>Once everything is deployed, we now have full CI\/CD enabled for our blog posts on Azure Static Websites and optimized using Azure CDN. If you need to assign a custom domain, it could be done in Azure CDN. Refer to this&nbsp;<a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/cdn\/cdn-map-content-to-custom-domain\">tutorial<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"validating-pull-requests-in-ci\">Validating Pull Requests in CI<\/h3>\n\n\n\n<p>It\u2019s a best practice to follow a git flow and work in a branch where code could be first built and tested before making it the master branch and eventually to deployment. In our setup we have included feature branches in a trigger for the Build pipeline. As Azure pipelines are now integrated with GitHub, we can see immediate feedback from the Build in the PR.<\/p>\n\n\n\n<p>To make sure that failed builds prevent the team from automatically merging the code disregarding the failed checks, setup GitHub policy in branch protection rules for the project \u2013 due to integration with Azure Pipelines we established earlier it would allow to assign the Build pipeline as the required check.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/protection-rule_17.png\" alt=\"Continuous Deployment Setup - screenshot 6\" class=\"wp-image-74135\" \/><\/figure>\n\n\n\n<p>Here is how the error in the code of the pull request is shown in GitHub \u2013 our Azure Pipelines Build kicked off and reported failure blocking the merge and requiring attention:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2018\/11\/gatsby-error_image-16.png\" alt=\"Continuous Deployment Setup - screenshot 7\" class=\"wp-image-74129\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"conclusion\">Conclusion<\/h3>\n\n\n\n<p>In this article we have demonstrated the power of the GatsbyJS JAMstack and Azure Pipelines. With just few tweaks, a scalable and cost-effective blog site is up and running on Azure Static Websites and it is integrated with a CI\/CD process. We could author blog posts in Git markdown and have the website automatically updated with new content. We showcased a \u201cPipeline as Code\u201d implementation, using Azure YAML pipelines, and how to use Docker images to bring the necessary Build tools. Terraform is great tool for infrastructure-as-code, but be aware that some preview features are not available and have to be dealt with.<\/p>\n\n\n\n<p>The full code for this article is in&nbsp;<a href=\"https:\/\/github.com\/lenisha\/great-gasby-blog\">GitHub here<\/a>.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"questions-or-feedback-please-let-us-know-in-the-comments\">Questions or feedback? Please let us know in the comments.<\/h4>\n","protected":false},"excerpt":{"rendered":"<p>This article will show how to build a blog (or any other static content) using a very popular&nbsp;JAMstack&nbsp;(GatsbyJS, GraphQL, Markdown) and host it on&nbsp;static website hosting for Azure Storage, which provides a cost effective and scalable solution for hosting static content and JavaScript code.<\/p>\n","protected":false},"author":5562,"featured_media":95484,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"msxcm_post_with_no_image":false,"ep_exclude_from_search":false,"_classifai_error":"","_classifai_text_to_speech_error":"","footnotes":""},"post_tag":[136,166,218],"content-type":[],"topic":[2240,2241,2244],"programming-languages":[2260],"coauthors":[2326],"class_list":["post-74125","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","tag-github","tag-azure","tag-terraform","topic-application-development","topic-cloud","topic-devops","programming-languages-javascript","review-flag-1593580428-734","review-flag-1593580771-946","review-flag-1-1593580432-963","review-flag-2-1593580437-411","review-flag-3-1593580442-169","review-flag-4-1593580448-609","review-flag-8-1593580468-572","review-flag-new-1593580248-669"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git<\/title>\n<meta name=\"description\" content=\"Walk through how to build a blog (or any other static content) using a JAMstack (GatsbyJS, GraphQL, Markdown) and host it with static website hosting for Azure Storage.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git\" \/>\n<meta property=\"og:description\" content=\"Walk through how to build a blog (or any other static content) using a JAMstack (GatsbyJS, GraphQL, Markdown) and host it with static website hosting for Azure Storage.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/\" \/>\n<meta property=\"og:site_name\" content=\"Microsoft Open Source Blog\" \/>\n<meta property=\"article:published_time\" content=\"2018-11-16T21:18:59+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-30T07:58:36+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/STB13_Allen_01.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1170\" \/>\n\t<meta property=\"og:image:height\" content=\"640\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Elena Neroslavskaya\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@OpenAtMicrosoft\" \/>\n<meta name=\"twitter:site\" content=\"@OpenAtMicrosoft\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Elena Neroslavskaya\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 min read\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/\"},\"author\":[{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/author\\\/elena-neroslavskaya\\\/\",\"@type\":\"Person\",\"@name\":\"Elena Neroslavskaya\"}],\"headline\":\"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git\",\"datePublished\":\"2018-11-16T21:18:59+00:00\",\"dateModified\":\"2025-06-30T07:58:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/\"},\"wordCount\":1943,\"commentCount\":3,\"publisher\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/STB13_Allen_01.webp\",\"keywords\":[\"GitHub\",\"Microsoft Azure\",\"Terraform\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/\",\"name\":\"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/STB13_Allen_01.webp\",\"datePublished\":\"2018-11-16T21:18:59+00:00\",\"dateModified\":\"2025-06-30T07:58:36+00:00\",\"description\":\"Walk through how to build a blog (or any other static content) using a JAMstack (GatsbyJS, GraphQL, Markdown) and host it with static website hosting for Azure Storage.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/#primaryimage\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/STB13_Allen_01.webp\",\"contentUrl\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/STB13_Allen_01.webp\",\"width\":1170,\"height\":640},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/2018\\\/11\\\/16\\\/terraform-jamstack-azure-gatsby-azure-pipelines-git\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/\",\"name\":\"Microsoft Open Source Blog\",\"description\":\"Open dialogue about openness at Microsoft \u2013 open source, standards, interoperability\",\"publisher\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#organization\",\"name\":\"Microsoft Open Source Blog\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/08\\\/Microsoft-Logo.png\",\"contentUrl\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/08\\\/Microsoft-Logo.png\",\"width\":259,\"height\":194,\"caption\":\"Microsoft Open Source Blog\"},\"image\":{\"@id\":\"https:\\\/\\\/opensource.microsoft.com\\\/blog\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/OpenAtMicrosoft\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git","description":"Walk through how to build a blog (or any other static content) using a JAMstack (GatsbyJS, GraphQL, Markdown) and host it with static website hosting for Azure Storage.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/","og_locale":"en_US","og_type":"article","og_title":"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git","og_description":"Walk through how to build a blog (or any other static content) using a JAMstack (GatsbyJS, GraphQL, Markdown) and host it with static website hosting for Azure Storage.","og_url":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/","og_site_name":"Microsoft Open Source Blog","article_published_time":"2018-11-16T21:18:59+00:00","article_modified_time":"2025-06-30T07:58:36+00:00","og_image":[{"width":1170,"height":640,"url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/STB13_Allen_01.png","type":"image\/png"}],"author":"Elena Neroslavskaya","twitter_card":"summary_large_image","twitter_creator":"@OpenAtMicrosoft","twitter_site":"@OpenAtMicrosoft","twitter_misc":{"Written by":"Elena Neroslavskaya","Est. reading time":"10 min read"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/#article","isPartOf":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/"},"author":[{"@id":"https:\/\/opensource.microsoft.com\/blog\/author\/elena-neroslavskaya\/","@type":"Person","@name":"Elena Neroslavskaya"}],"headline":"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git","datePublished":"2018-11-16T21:18:59+00:00","dateModified":"2025-06-30T07:58:36+00:00","mainEntityOfPage":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/"},"wordCount":1943,"commentCount":3,"publisher":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#organization"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/#primaryimage"},"thumbnailUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/STB13_Allen_01.webp","keywords":["GitHub","Microsoft Azure","Terraform"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/","url":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/","name":"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git","isPartOf":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/#primaryimage"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/#primaryimage"},"thumbnailUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/STB13_Allen_01.webp","datePublished":"2018-11-16T21:18:59+00:00","dateModified":"2025-06-30T07:58:36+00:00","description":"Walk through how to build a blog (or any other static content) using a JAMstack (GatsbyJS, GraphQL, Markdown) and host it with static website hosting for Azure Storage.","breadcrumb":{"@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/#primaryimage","url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/STB13_Allen_01.webp","contentUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2024\/06\/STB13_Allen_01.webp","width":1170,"height":640},{"@type":"BreadcrumbList","@id":"https:\/\/opensource.microsoft.com\/blog\/2018\/11\/16\/terraform-jamstack-azure-gatsby-azure-pipelines-git\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/opensource.microsoft.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Tutorial: Terraforming your JAMstack on Azure with Gatsby, Azure Pipelines, and Git"}]},{"@type":"WebSite","@id":"https:\/\/opensource.microsoft.com\/blog\/#website","url":"https:\/\/opensource.microsoft.com\/blog\/","name":"Microsoft Open Source Blog","description":"Open dialogue about openness at Microsoft \u2013 open source, standards, interoperability","publisher":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/opensource.microsoft.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/opensource.microsoft.com\/blog\/#organization","name":"Microsoft Open Source Blog","url":"https:\/\/opensource.microsoft.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/opensource.microsoft.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/08\/Microsoft-Logo.png","contentUrl":"https:\/\/opensource.microsoft.com\/blog\/wp-content\/uploads\/2019\/08\/Microsoft-Logo.png","width":259,"height":194,"caption":"Microsoft Open Source Blog"},"image":{"@id":"https:\/\/opensource.microsoft.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/x.com\/OpenAtMicrosoft"]}]}},"msxcm_animated_featured_image":null,"bloginabox_display_generated_audio":false,"distributor_meta":false,"distributor_terms":false,"distributor_media":false,"distributor_original_site_name":"Microsoft Open Source Blog","distributor_original_site_url":"https:\/\/opensource.microsoft.com\/blog","push-errors":false,"_links":{"self":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/74125","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/users\/5562"}],"replies":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/comments?post=74125"}],"version-history":[{"count":2,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/74125\/revisions"}],"predecessor-version":[{"id":97848,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/posts\/74125\/revisions\/97848"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/media\/95484"}],"wp:attachment":[{"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/media?parent=74125"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/post_tag?post=74125"},{"taxonomy":"content-type","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/content-type?post=74125"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/topic?post=74125"},{"taxonomy":"programming-languages","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/programming-languages?post=74125"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/opensource.microsoft.com\/blog\/wp-json\/wp\/v2\/coauthors?post=74125"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}