img2.png

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

1 week ago New!

3 min read


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 there's 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 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 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::[email protected]:adinhodovic/terraform-cloudflare-maintenance.git?ref=v0.1.3"
  enabled         = false
  cloudflare_zone = "hodovi.cc"
  pattern         = "hodovi.cc/maintenance/*"
  company_name    = "HoneyLogic"
  email           = "[email protected]"
  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!


Similar Blogs

10 months ago
mailgun statuscake terraform cloudflare devops s3 rds django

Kickstarting Infrastructure for Django Applications with Terraform

8 min read

When creating Django applications or using cookiecutters as Django Cookiecutter you will have by default a number of dependencies that will be needed to be created as a S3 bucket, a Postgres Database and a …


10 months ago Popular post!
devops gitlab-ci kaniko automation ci/cd

Creating templates for Gitlab CI Jobs

4 min read

Writing Gitlab CI templates becomes repetitive when you have similar applications running the same jobs. If a change to a job is needed it will be most likely needed to do the same change in …


8 months ago
prometheus kubernetes devops

Monitoring Kubernetes InitContainers with Prometheus

1 min read

Kubernetes InitContainers are a neat way to run arbitrary code before your container starts. It ensures that certain pre-conditions are met before your app is up and running. For example it allows you to: - …