Notes by Matt Stubbs

Building a lightweight cron job with Cloudflare Workers

Imagine you have thousands and thousands of commodity servers running in data centers all over the world. These servers are close to the end user so the response time is fast. Wouldn’t it be great if you could write scripts that run on these servers? Make sure it’s distributed, so you deploy once and it’s available in every location. But it’s also cheap, secure, and fast.

I needed somewhere to run a little cron job every five minutes. Cloudflare workers seemed like a good fit.

What Cloudflare offers

Cloudflare Workers are serverless functions: you write scripts, upload them to Cloudflare and they will be run when requested. The request could be an HTTP request from a browser. It can also be a request from another worker: you can connect different workers with service bindings and then call one worker from another. You call another worker by making a request:

// call highScoreWorker from another bound worker
const workerResponse = await highScoreWorker.fetch(request)

In Cloudflare requests are everywhere.

Is it cheap to run Cloudflare Workers?

Maybe. It depends. A free plan is available with 100,000 requests/day, with a max execution time of 10 ms. This is pretty good for free, and if you’re running scripts for your hobby projects this is probably enough.

The paid plan starts at $5/month which works out at $0.50 / million requests, max execution time of 50 ms (by default). This sounds cheap, and it might be, but the problem with serverless computing is you don’t really know what your usage is going to be like in advance. For $5 you can also get a shared VM with 1GB ram that runs day and night. That might be cheaper, but it also might be more work managing the server for small tasks.

There are other limits, too. A worker can use a maximum of 128 MB RAM, and on the free plan, there are limits to the number of workers, the number of scripts, and lots of other edge cases. The $5/month paid workers plan significantly increases most of these.

Not quite Node.js

Cloudflare Workers can run JavaScript, Typescript, or WebAssembly. If you’re writing a worker in JavaScript you might think you’re you can write a script just like you would for Node.js. I certainly did, and then I found out that while Cloudflare workers run the V8 engine, just like Node.js, the platform has its own runtime which is not exactly the same. Many useful APIs are available, like fetch() and the Streams API, but if you try to include any npm package you might find it depends on an API that is not available. I ran into this problem trying to use aws-sdk and it failed. My solution was to switch to aws4fetch which provides a lower level API but fewer dependencies. Cloudflare Workers also have a polyfill option which provides many missing Node.js APIs, but not all of them.

What about logging?

Debugging isn’t much fun without logging. Cloudflare Workers offers a couple of options. You can tail a worker from the console with wrangler tail and you’ll see realtime console output from your worker. If you want to store logs somewhere you’ll need a paid workers plans which gives you access to Logpush, Cloudflares log shipping service. Logpush will batch together log messages and deliver them to a logging service. Several providers are supported (Datadog, Google BigQuery, and a few more). You can also log to Amazon S3 or Cloudflare’s own R2 storage service.

Running a super cheap cron job doesn’t require anything too sophisticated, and the message volume is going to be low. I had a look around for cheap options and came across BetterStack. They support Cloudflare Logpush but also offer an HTTP API. Fire a request and you are logging. BetterStack provides sample code](https://betterstack.com/docs/logs/cloudflare/worker/) to include in your worker.

Setting up a Cloudflare Tunnel for Shopify app development

Cloudflare offers a service called Tunnel. It does lots of secure, enterprisey things but you can also use it to connect a domain to your local development environment. You install a client, run it and specify the local port you want traffic to connect to. Cloudflare will assign a temporary domain (something like abcd-1234.cfargotunnel.com). When you open https://abcd-1234.cfargotunnel.com in your browser Cloudflare forwards the request to your local machine on the port you specify.

You need a Cloudflare account, and to install the cloudflared command line tool. This tool connects your local machine to the Cloudflare network.

Quick tunnels

You then setup a temporary tunnel by running cloudflared tunnel --url http://localhost:8080

You’ll get a response with a tunnel id:

> cloudflared tunnel --url http://localhost:8080
Use `cloudflared tunnel run` to start tunnel 8921403b-dd2f-1234-b2be-d29663710123

Run cloudflared tunnel run <YOUR_TUNNEL_ID> to start the connection. The tunnel URL will be the tunnel id from the previous command, followed by cfargotunnel.com. For example, the tunnel above would be accessible at https://8921403b-dd2f-1234-b2be-d29663710123.cfargotunnel.com. This is the URL you can use when creating Shopify webhooks, or configuring OAuth URLs for testing.

Cloudflare tunnels with custom domains

You can also use your own domain by setting a CNAME record that maps to the tunnel subdomain. You can create a configuration file that maps requests from a domain to a local port.

The configuration goes in your ~/.cloudflared/config.yaml on a Mac. The first two lines are the tunnel id and credentials file, followed by ingress rules. For example:

tunnel: 8921403b-dd2f-1234-b2be-d29663710123
credentials-file: /Users/matt/.cloudflared/8921403b-dd2f-1234-b2be-d29663710123.json

ingress:
  - hostname: test.batchbrew.app
    service: http://localhost:4000
  - service: http_status:404%

Then add a CNAME record for the domain:

Disable Elixir Logger for a given process

It can be useful to disable the Elixir Logger output for a function that runs frequently, particularly if the function is recalled frequently or repetitively.

To disable the logger for the current process set the log level to none:

Logger.put_process_level(self(), :none)

Any other processes will continue to log with the default level.

Almost Full Ssl With Cloudflare And Amazon S3

Storing static content on S3 and then putting Cloudflare in front of it is an easy and cheap way to serve content fast.

Serving the content is easy enough: create a bucket in S3 that matches the domain name and create a CNAME record in Cloudflare:

CNAME static.exampleapp.com static.exampleapp.com.s3-website-us-west-2.amazonaws.com

But say you’ve got a subdomain for static content, like static.exampleapp.com, and want to use SSL for root domain exampleapp.com things get a little more complicated. S3 supports SSL from a virtual host but you need to use a bucket name without any . characters (so you can have static-exampleapp.exampleapp.com but that’s not as neat.

After repeated googling the most recommended option was to to configure ‘Flexible SSL’ in Cloudflare for the whole site. This fixes the problem: Cloudflare will use plain HTTP to request the content from S3 then serve it over HTTPS to the end user. This is pretty good: the user has a secure connection all the way to the CDN and, since it is static content, it’s probably fine to have the last mile connection to S3 unencrypted.

But Flexible SSL affects the whole site. Any requests on the root domain, where an application might be living that contains private data, will also only be secure up to Cloudflare.

Almost Full SSL with a page rule.

The easiest option is to leave Full SSL enabled for the site and then create a page rule to use Flexible SSL just for the subdomain.

Screenshot of the Cloudflare page rule UI

This means requests to https://exampleapp.com are secure all the way to the origin server. Requests to https://static.exampleapp.com are partly secure. Cloudflare uses HTTP to make the actual request to S3 which is fine for publicly available content like site stylesheets. The response is sent back to the user over HTTPS.