James Stanley

Solving Protohackers with Fly.io

Sat 24 September 2022
Tagged: software, protohackers

This post walks you through hosting a Go solution for the Protohackers Smoke Test problem using Fly.io for free hosting.

Fly.io can host anything that can run in a Docker container, and you don't even need to write your own Dockerfile because it automatically handles the most common languages and frameworks. I wish the tooling were less verbose, it's as if it's got debug mode logging permanently enabled. It is also quite slow. In principle we don't need any more than building a tiny binary and scping it to a server, why does it take multiple minutes to make a deployment?

Using Fly.io is slightly more trouble than using a normal VPS, so if you already have a VPS you will probably find that easier. Depending on your ISP, your router, and how comfortable you are with port forwarding, Fly.io could be either more or less trouble than hosting your program at home. If you think hosting at home sounds easy, then that is probably easier for you than Fly.io.

Let's assume that you don't want to pay for a VPS, and you can't host at home (maybe you are behind CGNAT). In that case Fly.io is your next best option!

Firstly, install Fly.io using the instructions on their website, which is currently:

$ curl -L https://fly.io/install.sh | sh

Follow its instructions to put its environment variables in your bashrc. Signup for Fly.io:

$ fly auth signup

This will try to open a page in your browser to let you either connect a GitHub account or sign up with email.

Make a directory for your project and copy the Go example program from the bottom of the Protohackers help page into main.go.

Fly.io is supposed to be able to auto-detect a Go program and deploy it with just fly launch, but I have found it doesn't auto-detect Go unless you have a go.mod. (You may have better luck with other languages). To help it detect your Go program, run:

$ go mod init example.com/foo
go: creating new go.mod: module example.com/foo

Now we'll launch it on Fly.io. I accepted the defaults for everything.

$ fly launch
Creating app in /home/jes/protofly
Scanning source code
Detected a Go app
Using the following build configuration:
	Builder: paketobuildpacks/builder:base
	Buildpacks: gcr.io/paketo-buildpacks/go
? App Name (leave blank to use an auto-generated name): 
Automatically selected personal organization: James Stanley
? Select region: lhr (London, United Kingdom)
Created app old-surf-8338 in organization personal
Wrote config file fly.toml
? Would you like to set up a Postgresql database now? No
? Would you like to deploy now? No
Your app is ready. Deploy with `flyctl deploy`

The default Fly.io configuration is to run an HTTP proxy in front of your application, but that's not what we want.

We need to edit the generated fly.toml file to make the [[services]] section expose TCP directly rather than proxying with HTTP. There is more information in their UDP and TCP documentation.

Clear out the [[services]] section of fly.toml and replace it with:

  internal_port = 10000
  protocol = "tcp"
    port = 10000

And deploy the application with fly deploy. This part is extremely verbose. It would benefit from the Rule of Silence. It also takes quite a long time. You just have to wait.

$ fly deploy
[... hundreds of lines redacted ...]
 1 desired, 1 placed, 1 healthy, 0 unhealthy
--> v2 deployed successfully

Once our application is "deployed successfully", it's up and listening on the public Internet. Despite the hundreds of lines of output that we were shown, I think we actually haven't been told what IP address our program is listening on. In my case the application was called old-surf-8338, and we can find out what IP address it's listening on by resolving old-surf-8338.fly.dev:

$ host old-surf-8338.fly.dev
old-surf-8338.fly.dev has address
old-surf-8338.fly.dev has IPv6 address 2a09:8280:1::a:77b4

So in my case we'll put and port 10000 into the checker form:

And the check should pass!

Great success indeed.

If you like my blog, please consider subscribing to the RSS feed or the mailing list: