Fly.io is a platform that lets you run your applications close to your users, essentially on servers scattered across the globe.

Let’s get an app running. We’ll use a simple Go web server that just prints "Hello, World!". First, make sure you have the Fly.io CLI installed. You can grab it from fly.io/docs/hands-on/install-flyctl/.

Now, create a new directory for your app and cd into it:

mkdir hello-fly
cd hello-fly

Inside this directory, create a file named main.go with the following content:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, World!")
	})

	port := "8080" // Default port, but Fly.io will override this
	log.Printf("Listening on port %s", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

This is a standard Go HTTP server. It listens on port 8080 by default, but Fly.io will inject a different port via an environment variable, which we’ll handle shortly.

Next, we need a fly.toml file. This file is Fly.io’s configuration. Run this command in your terminal from the hello-fly directory:

fly launch

This command does a few things:

  • It inspects your project and suggests an app name (you can change this).
  • It asks you if you want to create a new app.
  • It generates a fly.toml file for you.

Your fly.toml will look something like this, with your app name filled in:

# fly.toml file generated for hello-fly on 2023-10-27T10:00:00Z

app = "hello-fly"
primary_region = "ord" # Example: Chicago, USA

[build]
  builder = "heroku/buildpacks:20"

[deploy]
  strategy = "all"

[experimental]
  auto_rollback = true

The builder = "heroku/buildpacks:20" line tells Fly.io to use a standard buildpack system to figure out how to build your Go app. It’s intelligent enough to detect Go projects. The primary_region is where your app will initially be deployed. You can choose a region close to you or your users.

Now, let’s build and deploy your app. Run:

fly deploy

This command will:

  1. Build your Go application using the specified builder.
  2. Create a Docker image from the build output.
  3. Push that image to Fly.io’s global registry.
  4. Deploy the image to a virtual machine in the primary_region you specified.

You’ll see output indicating the build process, image pushing, and deployment. Once it’s done, you’ll get a URL like hello-fly.fly.dev.

Visit that URL in your browser, and you should see "Hello, World!".

The magic happens because Fly.io automatically sets the PORT environment variable inside the container to the port it wants your application to listen on. Your Go http.ListenAndServe function, when called without an explicit address (just ":"port"), respects the PORT environment variable if it’s set. If PORT is not set, it falls back to "8080" as defined in our main.go. Fly.io’s builder injects this PORT variable, so your app dynamically binds to the correct listening port.

If you want to see your app running locally before deploying, you can use fly dev. This command starts a local development server that mimics the Fly.io environment, including setting the PORT environment variable.

fly dev

This will build your app locally and run it, giving you a local URL to test against.

The most surprising thing about Fly.io’s deployment is how little configuration is actually required for many common application types. The fly launch command is designed to infer a lot from your project, and the buildpack system can often figure out how to build and run your code without explicit Dockerfiles or complex build steps. This drastically lowers the barrier to getting code running in a distributed environment.

Once your app is deployed, you can scale it up or down, change its region, or manage its secrets all through the flyctl CLI. For instance, to scale your app to two instances:

fly scale count 2

The next logical step is to explore how to manage persistent data for your application using Fly.io’s volumes.

Want structured learning?

Take the full Fly-io course →