Run a Static Website

Getting an application running on Fly.io is essentially working out how to package it as a deployable image. Once packaged, it can be deployed to the Fly.io platform.

In this guide we’ll learn how to deploy a static site on Fly.io.

In this demonstration, we’ll use nginx, the world’s most popular web server to serve a few static files with very little configuration. We’ll provide a Dockerfile and our content for Fly to transmogrify into a web server running in a VM.

You can clone all the files needed for this example from the hello-static GitHub repository to a local directory:

git clone https://github.com/fly-apps/hello-static

Alternatively, you can create all the files manually as you work through this guide.

Install flyctl and login


To configure our application and deploy it on Fly.io, we need flyctl, our CLI app for managing apps on Fly. If you’ve already installed it, carry on. If not, hop over to our installation guide. Once that’s installed you’ll want to log in to Fly.

Putting the app together


At this point, if you have a local clone of the hello-static repository, you could go ahead and run fly launch from its root directory and get the static site deployed without further ado. But that wouldn’t be very illuminating. Let’s go through what’s included in the example repository and why.

If you cloned the repository, your new app already has its own directory. Otherwise, create one. This isn’t just for tidiness, or for letting flyctl detect your app by the fly.toml in the working directory (although these are good reasons). It also ensures that no extra files get included in the build context when the Docker image gets built.

We’ll do everything from within this directory:

cd hello-static

The Site


Our example will be a simple static site. That can be as trivial as a single index.html file. Let’s make it only slightly more complicated by writing two HTML files and having them link to each other.

Put these HTML files into a subdirectory of their own, called public. Files in this directory are the ones our nginx server will serve. Create the hello-static/public directory if needed.

Here’s index.html, which is the landing page:

<html>
  <head>
    <title>
      Hello from Fly
    </title>
  </head>
  <body>
    <h1>Hello from Fly with a static web site</h1>
      <p>Or <a href="goodbye.html">goodbye.</a></p>
  </body>
</html>

Here’s goodbye.html.

<html>
  <head>
    <title>
      Still Hello from Fly
    </title>
  </head>
  <body>
    <h1>You say goodbye</h1>
      <p>But I say <a href="/">hello.</a></p>
  </body>
</html>

The Dockerfile


nginx can be run in a container, and the official nginx image is available on Docker Hub. This is super convenient for us, because Fly apps use container images!

We can use the nginx image as a base image. We just have to copy our site’s files to /usr/share/nginx/html in the image.

Here’s our Dockerfile to do that:

FROM nginx:alpine

COPY public /usr/share/nginx/html

The Dockerfile should be placed in the working directory (here, hello-static).

Configuring the App for Fly.io


We set the initial configuration for the app by running flyctl launch. This takes care of setting the app name, the Fly.io organization it belongs to, and a region to deploy to. It also generates a fly.toml file with more configuration settings.

The hello-static repository contains a fly.toml that will be detected by flyctl launch; you can use it to configure your app. Otherwise, a new fly.toml will be generated with flyctl launch, and we can edit it.

flyctl launch
Scanning source code
Detected a Dockerfile app
Creating app in /Users/jphenow/workspace/hello-static
We're about to launch your app on Fly.io. Here's what you're getting:

Organization: Jon Phenow                         (fly launch defaults to the personal org)
Name:         hello-static                       (generated)
Region:       Denver, Colorado (US)              (this is the fastest region for you)
App Machines: shared-cpu-1x, 1GB RAM             (most apps need about 1GB of RAM)
Postgres:     <none>                             (not requested)
Redis:        <none>                             (not requested)
Tigris:       <none>                             (not requested)

? Do you want to tweak these settings before proceeding? No
Created app 'hello-static' in organization 'personal'
Admin URL: https://fly.io/apps/hello-static
Hostname: hello-static.fly.dev
Wrote config file fly.toml
Validating /Users/jphenow/workspace/hello-static/fly.toml
✓ Configuration is valid
==> Building image
Remote builder fly-builder-little-haze-7293 ready
Remote builder fly-builder-little-haze-7293 ready
==> Building image with Docker
--> docker host: 24.0.7 linux x86_64
[+] Building 0.4s (7/7) FINISHED
 => [internal] load .dockerignore                                                                                                                                                                                                                                                                                                                                                0.2s
 => => transferring context: 100B                                                                                                                                                                                                                                                                                                                                                0.2s
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                                                                                                                                             0.2s
 => => transferring dockerfile: 90B                                                                                                                                                                                                                                                                                                                                              0.2s
 => [internal] load metadata for docker.io/library/nginx:alpine                                                                                                                                                                                                                                                                                                                  0.1s
 => [internal] load build context                                                                                                                                                                                                                                                                                                                                                0.1s
 => => transferring context: 151B                                                                                                                                                                                                                                                                                                                                                0.1s
 => [1/2] FROM docker.io/library/nginx:alpine@sha256:a5127daff3d6f4606be3100a252419bfa84fd6ee5cd74d0feaca1a5068f97dcf                                                                                                                                                                                                                                                            0.0s
 => CACHED [2/2] COPY public /usr/share/nginx/html                                                                                                                                                                                                                                                                                                                               0.0s
 => exporting to image                                                                                                                                                                                                                                                                                                                                                           0.0s
 => => exporting layers                                                                                                                                                                                                                                                                                                                                                          0.0s
 => => writing image sha256:1e96a09c0ae89bb9b8dc5dcdcfde700fb1ee2677c20dc9d5f324a5b768328dda                                                                                                                                                                                                                                                                                     0.0s
 => => naming to registry.fly.io/hello-static:deployment-01J8071W7G3S6R2MC5JJX915CC                                                                                                                                                                                                                                                                                              0.0s
--> Building image done
==> Pushing image to fly
The push refers to repository [registry.fly.io/hello-static]
deployment-01J8071W7G3S6R2MC5JJX915CC: digest: sha256:2ba357ed3fcf6eeb3a5e4c7a6e2300040cf6f414db69ce2e3b7d6030507deb5e size: 2196
--> Pushing image done
image: registry.fly.io/hello-static:deployment-01J8071W7G3S6R2MC5JJX915CC
image size: 43 MB

Watch your deployment at https://fly.io/apps/hello-static/monitoring

Provisioning ips for hello-static
  Dedicated ipv6: 2a09:8280:1::46:a909:0
  Shared ipv4: 66.241.124.149
  Add a dedicated ipv4 with: fly ips allocate-v4

This deployment will:
 * create 2 "app" machines

No machines in group app, launching a new machine

WARNING The app is not listening on the expected address and will not be reachable by fly-proxy.
You can fix this by configuring your app to listen on the following addresses:
  - 0.0.0.0:8080
Found these processes inside the machine with open listening sockets:
  PROCESS                                    | ADDRESSES
---------------------------------------------*----------------------------------------
  nginx: master process nginx -g daemon off; | 0.0.0.0:80, [::]:80
  /.fly/hallpass                             | [fdaa:0:7300:a7b:d827:5e53:ee2d:2]:22
  nginx: worker process                      | 0.0.0.0:80, [::]:80

Creating a second machine to increase service availability
Finished launching new machines
-------
NOTE: The machines for [app] have services with 'auto_stop_machines = "stop"' that will be stopped when idling

-------
Checking DNS configuration for hello-static.fly.dev

Visit your newly deployed app at https://hello-static.fly.dev/

This has configured the app with some default parameters, generated a fly.toml, attached a shared IPv4, created a dedicated IPv6, and deployed our image file for us.

Before the app will work though, we need to do one more thing. nginx listens on port 80 by default, but the default fly.toml assumes port 8080. Edit internal_port in the services section to reflect this:

[http_service]
  internal_port = 80
  force_https = true
  auto_stop_machines = 'stop'
  auto_start_machines = true
  min_machines_running = 0
  processes = ['app']

Now we’re ready to deploy:

flyctl deploy

The output should end something like this, if everything has gone well:

==> Verifying app config
Validating /Users/jphenow/workspace/hello-static/fly.toml
✓ Configuration is valid
--> Verified app config
==> Building image
Remote builder fly-builder-little-haze-7293 ready
Remote builder fly-builder-little-haze-7293 ready
==> Building image with Docker
--> docker host: 24.0.7 linux x86_64
[+] Building 0.4s (7/7) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                                                                                                                                                                                                                             0.2s
 => => transferring dockerfile: 90B                                                                                                                                                                                                                                                                                                                                              0.2s
 => [internal] load .dockerignore                                                                                                                                                                                                                                                                                                                                                0.2s
 => => transferring context: 100B                                                                                                                                                                                                                                                                                                                                                0.1s
 => [internal] load metadata for docker.io/library/nginx:alpine                                                                                                                                                                                                                                                                                                                  0.1s
 => [internal] load build context                                                                                                                                                                                                                                                                                                                                                0.1s
 => => transferring context: 151B                                                                                                                                                                                                                                                                                                                                                0.1s
 => [1/2] FROM docker.io/library/nginx:alpine@sha256:a5127daff3d6f4606be3100a252419bfa84fd6ee5cd74d0feaca1a5068f97dcf                                                                                                                                                                                                                                                            0.0s
 => CACHED [2/2] COPY public /usr/share/nginx/html                                                                                                                                                                                                                                                                                                                               0.0s
 => exporting to image                                                                                                                                                                                                                                                                                                                                                           0.0s
 => => exporting layers                                                                                                                                                                                                                                                                                                                                                          0.0s
 => => writing image sha256:1e96a09c0ae89bb9b8dc5dcdcfde700fb1ee2677c20dc9d5f324a5b768328dda                                                                                                                                                                                                                                                                                     0.0s
 => => naming to registry.fly.io/hello-static:deployment-01J807A99M034C3P47SBXFZZVZ                                                                                                                                                                                                                                                                                              0.0s
--> Building image done
==> Pushing image to fly
The push refers to repository [registry.fly.io/hello-static]
052eb83bbfc7: Layer already exists
b0f60355fd52: Layer already exists
027907faf592: Layer already exists
11134cc97d7f: Layer already exists
f7a5847cdca9: Layer already exists
aec1e8cf14f5: Layer already exists
717b3a077b07: Layer already exists
2ff96b2e5450: Layer already exists
63ca1fbb43ae: Layer already exists
deployment-01J807A99M034C3P47SBXFZZVZ: digest: sha256:2ba357ed3fcf6eeb3a5e4c7a6e2300040cf6f414db69ce2e3b7d6030507deb5e size: 2196
--> Pushing image done
image: registry.fly.io/hello-static:deployment-01J807A99M034C3P47SBXFZZVZ
image size: 43 MB

Watch your deployment at https://fly.io/apps/hello-static/monitoring

-------
Updating existing machines in 'hello-static' with rolling strategy

-------
 ✔ [1/2] Cleared lease for 3287eedf034558
 ✔ [2/2] Cleared lease for 4d89447f4117d8
-------
Checking DNS configuration for hello-static.fly.dev

Visit your newly deployed app at https://hello-static.fly.dev/

Viewing your static site


The quickest way to browse your newly deployed application is with the flyctl apps open command.

flyctl apps open
opening https://hello-static.fly.dev ...

Your browser will be sent to the displayed URL.