
In this tutorial, we’ll learn how to use Semaphore to deploy serverless functions to Cloudflare. With serverless functions, developers can build production-ready applications that scale without having to manage infrastructure.
Cloudflare Workers live on the edge of their network and can intercept and modify HTTP requests. Thus, we can use them to augment our websites, create new applications and services, or redirect and load-balance traffic.
As you transition from the “server full” to the serverless mindset, you’ll stop fretting about the how and start thinking in terms of the what. Your focus can move from managing infrastructure—whether they are servers or containers—to writing code. It’s on the cloud provider to figure out how to run it.
Prerequisites
You’ll need to set up a few things before reading on—after all, this is a hands-on tutorial.
First, install Git and sign up for a GitHub account. Next, you can sign up for Semaphore with your GitHub login. Last but not least, you need a Cloudflare account with an active domain proxied through the service.
If you plan to test and develop on your machine, you should also install Node.js v10.16.2.
Connect Semaphore with Cloudflare
We need to share the Cloudflare account details with Semaphore, which entails generating an API Token:
- Go to Cloudflare and sign-in.
- Select your domain and go to the Overview tab.
- Scroll down to the API section. Copy the Zone ID and Account ID.
- Click on Get your API Key and copy the displayed email address.
- Go to the API Tokens tab.
- Click on the View button next to the Global API Key. Copy the authorization key.
The token acts like a password, so we need to keep it off the repository. Semaphore provides secrets as a secure mechanism to store sensitive data.
To create a secret from the Semaphore website, click on Secrets in the left navigation bar. Create the secret as shown with your Cloudflare account details:

Secret details
- Name: cloudflare
- CLOUDFLARE_AUTH_EMAIL: YOUR_EMAIL
- CLOUDFLARE_AUTH_KEY: YOUR_API_KEY
- CLOUDFLARE_ACCOUNT_ID: YOUR_ACCOUNT_ID
- CLOUDFLARE_ZONE_ID: YOUR_ZONE_ID
Next, we’ll learn how to export the secrets in the job.
Cloudflare Workers and Serverless
In this section, we will learn how the serverless application works on Cloudflare.
Begin by forking the repository; first, go to Semaphore demo and use the Fork button. Click on Clone or download to get the URL and clone it to your machine:
$ git clone https://github.com/...
Then, install the dependencies in your machine:
$ npm install
Finally, link your new repository with Semaphore. To do it, click on the + (plus) icon next to Project and use the Add Repository button.
Let’s take a few minutes to examine the code. The main function can be found at hello.js:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
return new Response('Hello World!');
}
Oh yes, here we have the archetypical hello world example, only this time in the serverless form. The function replies “Hello World!” to any HTTP request, and that’s about it.
To understand how serverless deployment works, we must zoom back and learn about the Serverless framework. Initially developed for AWS Lambda, the framework hides all the cloud provider quirks and boils down the description of the deployment into a single manifest file. Since its creation, many other providers have started supporting it.
Open the manifest, serverless.yml
to take a quick look. The service
defines the service name and the provider-specific account details. Here is the definition for Cloudflare:
service:
name: semaphore-demo-cloudflare-workers
config:
accountId: $CLOUDFLARE_ACCOUNT_ID
zoneId: $CLOUDFLARE_ZONE_ID
provider:
name: cloudflare
stage: prod
plugins:
- serverless-cloudflare-workers
Then, we need to define the functions
that we want to call and the
events
that trigger them. In our example, requests to
example.com/hello
are replied with the response of the hello
function (the .js
extension can be omitted):
functions:
hello:
name: hello
worker: hello
script: hello
events:
- http:
url: example.com/hello
method: GET
Continuous Integration and Deployment
For all its benefits, serverless has its downsides. Perhaps the biggest one is that testing gets more challenging. The cloud runtime is hard to replicate in a test environment. Nevertheless, we’ll try to simulate Cloudflare’s environs as best as we can inside Semaphore.
The CI/CD workflow will test the code on every update and then, provided there aren’t any errors, deploy it to Cloudflare. Here’s the complete workflow:

We’ll learn about the testing pipeline next.
Testing in Semaphore
As I said before, Continuous Integration
(CI) is all about
testing. Let’s examine how the CI pipeline does it. Take a peek at the
pipeline file located at .semaphore/semaphore.yml
:
version: v1.0
name: CI
agent:
machine:
type: e1-standard-2
os_image: ubuntu1804
To make sure our CI environment is clean, Semaphore runs each job inside its own virtual machine. We have some machines types to choose from, which, combined with an Ubuntu 18.04 image, make a complete platform to power the pipelines.
In Semaphore, the execution order is organized by jobs
and blocks
.
Jobs run in parallel. Once all jobs in a block are done, the next block
begins. If any job fails, then the workflow stops with an error.
The prologue
is executed before each job; in our case, we’re using nvm to set the active Node.js version and npm to install the modules:
blocks:
- name: Install dependencies
task:
jobs:
- name: npm install
commands:
- checkout
- nvm use
- node -v
- cache restore
- npm install
- cache store
Checkout clones the GitHub repository, while cache detects the project structure and figures out which files should be cached for future runs. We’ll use cache store
and cache retrieve
to share the node dependencies between jobs.
The second block has two test jobs:
- Lint: uses JSHint, a code quality tool, to scan the files for code smells, that is, messy code that can cause bugs or decrease readability. Unused variables, missing semicolons, and using globals are all examples of funky code.
- Unit tests: to replicate the Cloudflare environment in Semaphore, we are using the Cloudworker project. This library makes the code behave as if it were running in the Cloudflare cloud.
- name: Run tests
task:
prologue:
commands:
- checkout
- cache restore
- nvm use
- node -v
jobs:
- name: Lint
commands:
- npm run lint
- name: Unit tests
commands:
- npm test
The final section in the pipeline defines a
promotion.
A promotion chain pipelines together to create more complex workflows.
Here, we are using auto_promote_on
to start the next pipeline when the
branch is master
and there aren’t any errors.
promotions:
- name: Deploy
pipeline_file: deploy.yml
auto_promote_on:
- result: passed
branch:
- master
See you on the deployment pipeline.
Deploying with Semaphore
Few things feel better than fully-tested code. It fills me with determination to continue with the deployment. The Continuous Deployment pipeline starts immediately after the integration ends.
You have to customize serverless.yml
a bit now. Open the file and locate the url
key. Replace example.com
with your Cloudflare domain and type the URL you wish to trigger the function with. You may use wildcards such as example.com/*
or *.example.com
.
Open .semaphore/deploy.yml
to review the deployment pipeline. I’ll skip the introduction section since we’ve already seen that, and head directly to the block:
blocks:
- name: Deploy
task:
secrets:
- name: cloudflare
prologue:
commands:
- checkout
- cache restore
- nvm use
- node -v
jobs:
- name: Deploy to Cloudflare
commands:
- cat serverless.yml | envsubst | tee serverless.yml
- npm run deploy
- npm run hello
The first command replaces the account details in serverless.yml
using the variables imported from the secret
.
The second command, npm run deploy
, calls the Serverless
CLI to start the deployment.
Within 30 seconds, the function should be up and running at Cloudflare.
The last command calls the function directly from the cloud to test that
it’s working.
Push to GitHub & Test
Push the code to GitHub to get the workflow started:
$ git add serverless.yml
$ git commit -m "deploy to cloudflare"
$ git push origin master
You can check the progress on Semaphore; after a few seconds, we should see the deployment block complete. Clicking on the last job in the workflow brings up the execution log:
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Starting Serverless Cloudflare-Worker deployment.
Serverless: Starting deployment
Serverless: deploying script: hello
Serverless: Finished deployment hello in 1.412 seconds
Serverless: ✅ Script Deployed. Name: hello, Size: 0.19K
Serverless: ✅ Routes Deployed
Serverless: Finished deployment in 18.061 seconds.
Serverless: Finished Serverless Cloudflare-Worker deployment.
exit code: 0 duration: 20s
Let’s see if it’s really working. Try visiting the function URL with your browser or making a request with curl. Replace example.com/hello
as appropriate:
$ curl -w "\n" example.com/hello
Hello World!
Well, hello to you too, little function.
Servers Have Their Days Counted
You’ve deployed your first serverless function. We’ve learned about its strengths and weaknesses, and how to balance them with Semaphore CI/CD. Isn’t its simplicity a breath of fresh air? If you are like me, once you try the serverless route, you’ll wish you could use it for everything.
Want more practice? Try browsing the Cloudflare gallery which has a lot of good templates to explore.
The post A CI/CD Pipeline for Serverless Cloudflare Workers appeared first on Semaphore.