Fly.io Machines are a powerful way to manage compute resources programmatically.

Let’s spin up a simple web server using the Machines API. We’ll use a minimal nginx image for this.

# First, create a directory for our machine configuration
mkdir nginx-machine
cd nginx-machine

# Create a config.json file
cat <<EOF > config.json
{
  "name": "my-nginx-machine",
  "region": "us-east",
  "image": "nginx:latest",
  "services": [
    {
      "ports": [
        {
          "port": 80,
          "handlers": ["http"]
        }
      ]
    }
  ]
}
EOF

# Now, create the machine using the flyctl CLI (which uses the Machines API under the hood)
flyctl machine create --config config.json

After running this, flyctl will output the machine ID. You can then check its status:

flyctl machine status <machine-id>

And if you curl the IP address assigned to the machine, you should see the default Nginx welcome page.

The core concept behind Machines is that they are ephemeral compute units that you can create, update, and destroy via an API. Unlike traditional Fly.io Apps, which abstract away the underlying machines, Machines give you direct control. You define the machine’s image, resources, networking, and even volume attachments.

Internally, when you create a machine, Fly.io provisions a lightweight VM (a Firecracker microVM) and launches your specified container image within it. The services array in the config.json defines how the machine interacts with the network. port: 80 and handlers: ["http"] tells Fly.io to expose port 80 and route HTTP traffic to it.

The primary levers you control are within the config.json file:

  • name: A human-readable name for your machine.
  • region: The geographic location where the machine will run.
  • image: The container image to run (e.g., nginx:latest, ghcr.io/myuser/myimage:v1.0).
  • resources: (Optional) Define CPU and memory limits (e.g., {"cpu_kind": "shared", "cpus": 1, "memory_mb": 512}).
  • services: Defines network exposure. Each service can have multiple ports and handlers (like http, tcp, tls).
  • mounts: (Optional) Attach persistent volumes for data storage.

When you update a machine, you’re essentially creating a new version of that machine with the updated configuration. Fly.io handles the graceful shutdown of the old machine and the startup of the new one, ensuring minimal downtime. This is achieved by creating a new machine with the same name and then updating the existing one to point to the new machine. The old machine is then eventually cleaned up.

The most surprising thing about managing Machines programmatically is how closely it mirrors infrastructure-as-code principles, but with the added benefit of Fly.io’s global edge network and integrated networking layer. You’re not just defining a VM; you’re defining a network-addressable compute resource that can be part of a larger distributed system, with automatic scaling and resilience built into the platform’s DNA. The services definition is particularly powerful; you can expose multiple ports, each with different handlers, allowing for complex application architectures to be deployed with straightforward configuration.

The next step in managing Machines programmatically is to explore how to attach persistent storage using mounts to keep data across machine restarts.

Want structured learning?

Take the full Fly-io course →