Deploying on a server (Fly.io)
Learning objectives
- You know how to deploy a web application.
Here, we briefly look into deploying a web application on the web using Fly.io. Although in the materials we use Fly.io, other options for deploying software are also accepted, including self-hosting content.
A note on deployment with Fly.io
This part provides similar guidelines to deployment as the previous part on Deploying on a server (Render). The key difference is that here, we outline how the same task is accomplished with Fly.io.
Note that Fly.io requires credit card (from some participants). When used within the free tier limits, the credit card will not be billed. We acknowledge that all participants do not have a credit card.
Register to Fly.io
To use Fly.io, you need to first register to the platform. Click the Sign In
button on the top right corner of the Fly.io page, click the Need an Account
link, and fill in your details. Alternatively, you can click this link to directly go to the sign up page. On the sign up page, fill in your details and click Create My Account
.
Do not sign up with GitHub.
Once signed up, you are shown a page into which you can fill in your credit card details. The platform has a free tier and instead of entering credit card details, you can click the Try Fly.io for free
link.

After registering, at least when you create an account without GitHub, Fly.io sends you an email to confirm the registration. Visit your email and confirm the registration.
The confirmation email might take a while to arrive.
Install flyctl
Once registered, you need to install the flyctl
command line tool used to deploy applications. Visit one of the following links and install the flyctl
command line tool.
The flyctl
command provides an easy access to creating and managing applications. Next, we look into deploying an application using the Walking Skeleton of the course.
Download walking skeleton
Download the source codes for the Walking Skeleton at https://github.com/avihavai/wsd-walking-skeleton. You can do this by clicking the Code
button on the walking skeleton page and choosing one of the download options. The simplest option might be downloading the walking skeleton as a zip file.
You can also use this link to download the zip file for the walking skeleton directly.
Decompress the zip file to a suitable directory. The contents of the directory should be as follows.
$ tree --dirsfirst
.
├── app
│ ├── app.js
│ └── Dockerfile
├── app-cache
│ └── README.txt
├── e2e-playwright
│ ├── tests
│ │ └── hello-world.spec.js
│ ├── Dockerfile
│ ├── package.json
│ └── playwright.config.js
├── flyway
│ └── sql
│ └── V1___initial_schema.sql
├── docker-compose.yml
├── project.env
└── README.md
Authenticating on command line
To authenticate on command line, we use flyctl auth login
. This opens up a browser window where we login to the Fly.io, identifying ourselves.
$ flyctl auth login
Opening https://fly.io/app/auth/cli/(hash) ...
Waiting for session... Done
successfully logged in as (your email)
Once authenticated, we can proceed.
Creating an application
Using the flyctl
, let's create an application. We use the configuration from the walking skeleton for deployment -- that is, Fly.io will create a Docker image based on our application that we can use to launch the application. To get started, we need to go to the folder app
in the walking skeleton. In the following example, we assume that we are in the folder that contains the contents of the walking skeleton.
$ ls
app/ app-cache/ docker-compose.yml e2e-playwright/ flyway/ project.env README.md
$ cd app
$ ls
app.js Dockerfile
In Windows, the dir
command acts similarly to the ls
command.
The app
folder contains two files, app.js
and Dockerfile
.
The contents of app.js
is as follows.
import { serve } from "https://deno.land/std@0.222.1/http/server.ts";
const handleRequest = (request) => {
console.log(`Request to ${request.url}`);
return new Response("Hello world!");
};
console.log("Launching server on port 7777");
serve(handleRequest, { port: 7777 });
To create an application with flyctl
, we use the command launch
. First, we verify that we are indeed in the app
folder, after which we create an application using flyctl launch
. The flyctl
command asks for an application name, for a region, whether we want to have a PostgreSQL database, and whether we want to deploy directly. Use a blank name for the application which leads to a randomly generated name, choose a region that you wish (below, Amsterdam in Netherlands is chosen), do not set up PostgreSQL, and do not deploy now.
$ ls
app.js Dockerfile
$ flyctl launch
Creating app in /path-to-walking-skeleton/wsd-walking-skeleton/app
Scanning source code
Detected a Dockerfile app
? App Name (leave blank to use an auto-generated name):
Automatically selected personal organization: (your name)
? Select region: ams (Amsterdam, Netherlands)
Created app aged-cherry-5206 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`
In the above example, the randomly chosen name was aged-cherry-5206
.
Creating an application with flyctl launch
leads to the generation of a configuration file, which will be placed to the present folder. The name of the file is fly.toml
. Now, there should be three files in the present folder.
$ ls
app.js Dockerfile fly.toml
Modifying fly.toml
By default, the file fly.toml
looks as follows. In the example below, the randomly chosen name for the application was aged-cherry-5206
.
# fly.toml file generated for aged-cherry-5206 on ...
app = "aged-cherry-5206"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8080 processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
When we look at the configuration, we notice that the internal_port
of the configuration has been set to 8080
. The row has also been highlighted above.
Although the configuration assumes that our application starts on port 8080
, the walking skeleton uses port 7777
. To accommodate for this, we need to adjust the configuration by changing the value of internal_port
to 7777
as follows.
# fly.toml file generated for aged-cherry-5206 on ...
app = "aged-cherry-5206"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 7777 processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
Done. Now, we can deploy our application.
Deploying and opening the application
Deployment of the application is done using flyctl
s deploy
command. The command is run in the same folder that contains the fly.toml
configuration (and the files app.js
and Dockerfile
).
$ ls
app.js Dockerfile fly.toml
$ flyctl deploy
==> ... (plenty of stuff)
==> ... (waiting and waiting)
1 desired, 1 placed, 1 healthy, 0 unhealthy
--> v0 deployed successfully
When the deployment has been successful, the output contains a string 1 healthy
. Now, we can open the application with the command flyctl open
, which opens the application in a new browser window.
$ flyctl open
opening (address to your application) ...
Now, the application is available online.

Modifying the application and redeploying
Now, our application has been deployed. Let's modify the application and redeploy it. Adjust the contents of the app.js
in the folder app
of the walking skeleton to match the following. Remember to save the file.
import { serve } from "https://deno.land/std@0.222.1/http/server.ts";
const handleRequest = (request) => {
return new Response("Meaning of life: 42");
};
serve(handleRequest, { port: 7777 });
Now, when you rerun the command flyctl deploy
, the application is redeployed.
$ ls
app.js Dockerfile fly.toml
$ flyctl deploy
==> ... (plenty of stuff)
==> ... (waiting and waiting)
1 desired, 1 placed, 1 healthy, 0 unhealthy
--> v1 deployed successfully
