Hello internet, the past few days I've been playing with fly.io. I must admit I'm not entirely sure why, but it seemed like a fun project. They even have a getting started project for deno. Sadly it uses something called dinatra which hasn't received a commit in 16 months. So that project seems just about dead.
Therefore I decided to take their starter project and make it my own. One of the other things I found frustrating is that flyctl (their command line utility) will automatically scan the project and create a dockerfile. This is cool, but their template is coded to use deno 1.14, which is about 17 months old. That's essentially an eternity in the open source world.
So what should we do to get started? Well, we'll need a server. Obviously I'm not going to use dinatra, since the project seems dead; more importantly, the standard library provides a perfectly good server out of the box.
Fly recommends:
import {
app,
get,
post,
redirect,
contentType,
} from "https://denopkg.com/syumai/dinatra/mod.ts";
const greeting = "<h1>Hello From Deno on Fly!</h1>";
app(
get("/", () => greeting),
get("/:id", ({ params }) => greeting + `</br>and hello to ${params.id}`),
);
Let's do our own version like:
server.sh
import { serve } from "https://deno.land/std@0.173.0/http/server.ts";
serve(() => {
return new Response(
`Hello from deno.`,
);
}, { port: 8080 });
My version is a bit shorter, but also includes slightly less functionality. Whatever.
We can test this by running:
deno run --allow-net server.ts
Great, we get a boring response when we do:
curl localhost:8080
Hello from deno.%
Now we have our (slightly, and in my opinion) improved version of the server implemented. But remember the issue about fly's template for deno projects? Let's avoid that and create our own dockerfile:
Dockerfile
FROM denoland/deno:1.30.3 as base
USER deno
WORKDIR /app
COPY . ./
RUN deno cache server.ts
CMD ["run", "--allow-net", "--allow-env", "--allow-read", "server.ts"]
At this point we should have a project looking like this:
.
├── Dockerfile
└── server.ts
Not terribly exciting. It's about to get... not so exciting. Let's try building our simple image:
docker build . -t deno_hello_fly
We can find our image by running
docker image ls
You should get something like:
REPOSITORY TAG IMAGE ID CREATED SIZE
deno_hello_fly latest 00270a10eba1 3 minutes ago 186MB
We have our new image. Let's start it:
docker run -p 8080:8080 deno_hello_fly
Well that's too hard for me to remember. Binding ports manually and specifying the name? Nothx. Let's wrap this all in a compose file, so that we can just use the same standard command to start things up.
docker-compose.yaml
version: "3.9"
services:
deno:
container_name: deno_hello_fly
image: deno_hello_fly
build:
context: .
dockerfile: Dockerfile
target: base
ports:
- 8080:8080
Now we don't have to think because we can just rely on docker-compose to do the heavy lifting:
docker-compose up
If we need to rebuild something, then that's easy:
docker-compose up --build
Lovely. We can now test everything locally. (Ok there's not so much to test right now, but maybe something will come later.)
Our project should look like this:
.
├── Dockerfile
├── docker-compose.yaml
└── server.ts
Let's get back to fly. They suggest the following:
fly launch
It will ask you a bunch of stuff and you should answer accordingly:
reed@reed test % fly launch
Creating app in /path/to/your/source
Scanning source code
Detected a Dockerfile app
? Choose an app name (leave blank to generate one): my-app-name
automatically selected personal organization: youremail@domain.com
? Choose a region for deployment: Stockholm, Sweden (arn)
Created app my-app-name in organization personal
Admin URL: https://fly.io/apps/my-app-name
Hostname: my-app-name.fly.dev
? Would you like to set up a Postgresql database now? No
? Would you like to set up an Upstash Redis database now? No
Wrote config file fly.toml
? Would you like to deploy now? No
Your app is ready! Deploy with `flyctl deploy`
This is great. This has created a .toml
file that looks like:
fly.toml
# fly.toml file generated for my-app-name on 2023-02-19T18:20:27+01:00
app = "my-app-name"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
[experimental]
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"
I must admit I haven't played around with these settings at all. Maybe we can try that later; instead I've raced on to the next step:
flyctl deploy
This command will build the container and send the image up to the interwebs. It then suggests to run fly info
. Sadly we immediately receive:
Command "info" is deprecated, Replaced by 'status', 'ips list', and 'services list'
App
Name = my-app-name
Owner = personal
Status = running
Version = 0
Platform = nomad
Hostname = my-app-name.fly.dev
Services
PROTOCOL PORTS
TCP 80 => 8080 [HTTP]
443 => 8080 [TLS, HTTP]
IP Addresses
TYPE ADDRESS REGION CREATED AT
v6 some:ip:address global 1m29s ago
It seems like they need to update their documentation to no longer suggest the deprecated command.
Instead fly status
gives:
App
Name = my-app-name
Owner = personal
Version = 0
Status = running
Hostname = my-app-name.fly.dev
Platform = nomad
Deployment Status
ID = big-long-id-here
Version = v0
Status = successful
Description = Deployment completed successfully
Instances = 1 desired, 1 placed, 1 healthy, 0 unhealthy
Instances
ID PROCESS VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED
1234asdf app 0 arn run running 1 total, 1 passing 0 1m27s ago
Great. We're almost there. Just one last command for now:
fly open
This should launch your app in a new browser window. Hopefully you see:
Hello from deno.
The end state of our project here is:
.
├── Dockerfile
├── docker-compose.yaml
├── fly.toml
└── server.ts
This seems like a long enough post for now. Tomorrow we'll get some more features. Look for thoughts about adding a custom domain name to our app, along with deploying it automatically every time we push to github.