Fly.io’s flyctl is a powerful CLI tool for deploying and managing applications on the Fly.io platform, but its real magic lies in how it abstracts away complex infrastructure, letting you focus on your code.

Here’s flyctl in action, deploying a simple Go web server:

First, create a main.go file:

package main

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

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello from Fly.io!")
	})

	port := "8080" // Default port
	log.Printf("Server starting on port %s\n", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

Next, create a fly.toml configuration file:

app = "my-go-app-12345" # Replace with your unique app name
kill_signal = "SIGINT"
restart_policy = "always"

[deploy]
  release_command = ""

[experimental]
  auto_assign_non_shared_ip = true

[[services]]
  protocol = "tcp"
  port = 80
  hosts = ["0.0.0.0"]

  [services.concurrency]
    type = "connections"
    hard_limit = 25
    soft_limit = 20

Now, log in to your Fly.io account:

flyctl auth login

Create the application on Fly.io:

flyctl apps create my-go-app-12345

Deploy your application:

flyctl deploy --config fly.toml

After deployment, you can check the status:

flyctl status --app my-go-app-12345

And access your application via its public URL, which flyctl will output, or by running:

flyctl open --app my-go-app-12345

This simple example demonstrates the core workflow: define your app, configure it, and deploy it. flyctl handles provisioning servers, setting up networking, and managing the lifecycle of your application.

The problem flyctl solves is the immense complexity of setting up and managing cloud infrastructure. Traditionally, you’d need to provision virtual machines, configure load balancers, manage DNS, set up SSL certificates, and orchestrate deployments. flyctl abstracts all of this, allowing you to treat your application as a deployable unit without needing deep infrastructure expertise.

Internally, flyctl communicates with the Fly.io API to create and manage resources. When you run flyctl deploy, it packages your application code, reads your fly.toml for configuration, and then instructs the Fly.io platform to build a Docker image (if not provided), provision virtual machines (called "VMs" or "proxies" on Fly.io), and run your application within those VMs. It also manages the routing of traffic to your application instances.

The fly.toml file is your primary lever for controlling how your application runs on Fly.io. The services section, for instance, defines how your application listens for incoming traffic. The port = 80 means it will listen on the standard HTTP port, and protocol = "tcp" specifies the network protocol. The concurrency settings allow you to fine-tune how many simultaneous connections your application can handle, preventing overload.

One aspect that often surprises people is how Fly.io handles scaling. While you can manually scale your app using flyctl scale count <number>, the platform also offers automatic scaling based on metrics like CPU utilization. This is configured through your fly.toml or via the Fly.io dashboard, and it means your application can dynamically adjust its capacity to meet demand without manual intervention. This automatic scaling is a significant departure from traditional VM-based deployments where you’d have to manually provision more instances.

Beyond deployment, flyctl offers commands for managing your deployed applications, such as flyctl logs to view real-time logs, flyctl restart to reboot your app’s VMs, and flyctl destroy to remove the application entirely.

The next concept to explore is managing persistent storage and databases with Fly.io.

Want structured learning?

Take the full Fly-io course →