DEV Community

Cover image for Quick, Pretty and Easy Maintenance Page using Cloudflare Workers & Terraform
Adin Hodovic
Adin Hodovic

Posted on • Originally published at hodovi.cc

Quick, Pretty and Easy Maintenance Page using Cloudflare Workers & Terraform

Maintenance pages are a neat way to inform your users that there are operational changes that require downtime. Cloudflare Workers allows you to execute Javascript and serve HTML close to your users and when Cloudflare manages your DNS it makes it easy to create a Worker and let it manage all traffic during a maintenance period. This makes a great solution for smaller teams/applications that do not want to invest time into creating and displaying maintenance pages. Terraform is a tool that enables provisioning infrastructure via code and it integrates great with Cloudflare APIs.

A preview of the final implementation can be found here, and I've created a Terraform module for creating the maintenence page.

Cloudflare Worker

The Worker's javascript is simple and it is found and explained in depth in this blog post. The snippet forwards the request to the Web Server if the connecting IP is whitelisted, otherwise it returns a html response that you've defined which in our case is the maintenance page.

async function fetchAndReplace(request) {

  let modifiedHeaders = new Headers()

  modifiedHeaders.set('Content-Type', 'text/html')
  modifiedHeaders.append('Pragma', 'no-cache')


  //Allow users from trusted into site
  if (white_list.indexOf(request.headers.get("cf-connecting-ip")) > -1)
  {
    //Fire all other requests directly to our WebServers
    return fetch(request)
  }
  else //Return maint page if you're not calling from a trusted IP
  {
    // Return modified response.
    return new Response(maintenancePage, {
      status: 503,
      headers: modifiedHeaders
    })
  }
}

let maintenancePage = `
<!doctype html>
<title>Site Maintenance</title>
<div class="content">
    <h1>We&rsquo;ll be back soon!</h1>
    <p>We&rsquo;re very sorry for the inconvenience but we&rsquo;re performing maintenance. Please check back soon...</p>
    <p>&mdash; Awesome Team</p>
</div>
`

Terraform

We'll now use Terraform to create the script and to create the worker route. Creating a worker script with Terraform can be done with the cloud_worker_script resource:

resource "cloudflare_worker_script" "this" {
  name    = "maintenance"
  content = file("/maintenance.js")
}

This will create a Cloudflare worker script which we can reference with the cloudflare_worker_route. The route resource will deploy the worker in a zone with a specified url pattern:

data "cloudflare_zones" "this" {
  filter {
    name = "hodovic.cc/*"
  }
}

resource "cloudflare_worker_route" "this" {
  zone_id     = lookup(data.cloudflare_zones.this.zones[0], "id")
  pattern     = "hodovi.cc/maintenance/*"
  script_name = cloudflare_worker_script.this.name
}

All users that visit the /maintenance/ path will be shown the maintenance page except for the whitelisted IPs.

Terraform Module

The solution presented above is neat, although we'd like a resuable module. Therefore, I've created a Terraform module that does the creation and deployment of a Cloudflare Worker. However, for the module to be reusable we need dynamic html content as:

  • Logo
  • Font
  • Company/Team name
  • Favicon
  • Email address for support

Cloudflare supports key/value storage which we can define in the Terraform module. We'll adjust the previous cloudflare_worker_script to add the key/value storages:

resource "cloudflare_worker_script" "this" {
  name    = "maintenance"
  content = file(format("%s/maintenance.js", path.module))

  plain_text_binding {
    name = "COMPANY_NAME"
    text = var.company_name
  }

  plain_text_binding {
    name = "WHITELIST_IPS"
    text = var.whitelist_ips
  }

  plain_text_binding {
    name = "LOGO_URL"
    text = var.logo_url
  }

  plain_text_binding {
    name = "FAVICON_URL"
    text = var.favicon_url
  }

  plain_text_binding {
    name = "FONT"
    text = var.font
  }

  plain_text_binding {
    name = "EMAIL"
    text = var.email
  }
}

We can create a HTML snippet and pass the environment variables:

let maintPage = (company_name, logo_url) => `
<body>
    <div class="content">
        <img class="logo" src="${logo_url}" alt="${company_name}">
        <div class="info">
            <h1>Our site is currently down for maintanence</h1>
            <p>We apologize for any inconveniences caused and we will be online as soon as possible. Please check again in a little while. Thank you!</p>
            <p>&mdash; ${company_name}</p>
        </div>
        <img class="image-main" src="https://i.imgur.com/0uJkCM8.png" alt="Maintenance image">
        <hr />
        <a href="mailto:${email}?subject=Maintenance">You can reach us at: ${email}</a>
    </div>
</body>
`;

We'll also create dynamic CSS for fonts:

body {
    text-align: center;
    font-family: "${font}", sans-serif;
    color: #0C1231;
}

The html/css snippets are more complex and dynamic and the full source code can be found on Github.

The module supports several variables:

  • Logo
  • Font (Google fonts)
  • Email
  • Team name
  • Whitelisting IPs

You can create it using Terraform's module:

module "hodovi_cc_maintenance" {
  source          = "git::git@github.com:adinhodovic/terraform-cloudflare-maintenance.git?ref=v0.1.3"
  enabled         = false
  cloudflare_zone = "hodovi.cc"
  pattern         = "hodovi.cc/maintenance/*"
  company_name    = "HoneyLogic"
  email           = "support@honeylogic.io"
  font            = "Poppins"
  logo_url        = "https://s3.eu-west-1.amazonaws.com/honeylogic.io/media/images/Honeylogic-blue.original.png"
  favicon_url     = "https://s3.eu-west-1.amazonaws.com/honeylogic.io/media/images/Honeylogic_-_icon.original.height-80.png"
}

A preview of the maintenance page can be found here, fully adjustable to your team needs!

Top comments (0)