惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
V
V2EX
博客园 - 三生石上(FineUI控件)
Hugging Face - Blog
Hugging Face - Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
罗磊的独立博客
博客园_首页
量子位
雷峰网
雷峰网
GbyAI
GbyAI
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The Cloudflare Blog
IT之家
IT之家
WordPress大学
WordPress大学
人人都是产品经理
人人都是产品经理
Apple Machine Learning Research
Apple Machine Learning Research
P
Proofpoint News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东

Learn Cloud Native

Plan-based validation for coding agents on Kubernetes | Learn Cloud Native PR validation at scale and a sandbox for every pull request on Kubernetes | Learn Cloud Native 7 practical MCP policies with agentgateway | Learn Cloud Native Agentgateway rate limiting for agents | Learn Cloud Native Local development with coding agents on Kubernetes using Signadot | Learn Cloud Native cuenv: one typed file for your whole project | Learn Cloud Native Preflight: AI Code Review Before You Push Anatomy of AI Agents Accessing Google Drive from Next.js Top Cloud-Native & Kubernetes Certifications [2026 Guide] Rapid microservices development with Signadot How to prepare for Istio certified associate exam (ICA) Global Rate Limiting in Istio with Envoy Rate Limit Service My Journey with Istio: From Incubation to Graduation Cilium Network Policy Tutorial: Secure Kubernetes Step by Step Kubernetes Networking: How kube-proxy and iptables Work Istio ServiceEntry: DNS vs. STATIC Resolution & Endpoints Explained Apply an Istio DestinationRule Globally (Mesh-Wide) Istio Rate Limiting: Configure a Local Rate Limiter in Envoy How to expose custom ports on Istio ingress gateway Portainer Tutorial: A Web UI for Kubernetes & Containers Traefik Proxy 2.x and TLS 101 Kubernetes CLI (kubectl) tips you didn't know about Setting up SSL certificates with Istio Gateway ArgoCD Best Practices You Should Know 在 OCI Ampere A1 计算实例上运行 AI Running AI On OCI Ampere A1 Instance How to Deploy Traefik Proxy Using Flux and GitOps Principles Firebase Emulators with Next.js: Local Setup Guide Running Hugo on free Ampere VM (Oracle Cloud Infrastructure)
Deploying to Fly.io using Dagger and Github
Peter Jausovec · 2024-07-20 · via Learn Cloud Native

I was looking for a simple service where I could, ideally, run a single CLI command and deploy a simple Javascript or Typescript server. One of the suggestions was Fly.io. I've later also tried Render, which works similarly.

Starting with Fly.io

The deployment to Fly.io is fairly straightforward. Once you install the CLI and configure your app (most of it is automatic, i.e. just confirm the defaults), you can deploy your app with a single command -- fly deploy. However, we could do better than that and do automatic deployments on every push to the main branch.

I was hoping Fly.io would integrate with GitHub automatically where I could select my repo and it would automatically do deploys. However, at the time of writing this, Fly.io didn't support that. Which means, you have to configure the GitHub action on your own. Let's see how we can do that!

Setup Github action for Fly.io

I am going to assume you have the CLI and app set up already. If not, you can fork my todo app as an example. Alternatively, create a simple server yourself with bun create elysia app. You can follow the instructions here.

Back to Github actions.

To deploy to Fly.io from a Github action you'll need a token to authenticate with Fly.io. You can do that by running the fly CLI inside your app folder:

fly tokens create deploy -x 999999h

Take the output of the command and go to your Github repo to store it as a repository secret. This will make the token available for use in the Github action.

Open your Github repository and:

  1. Click Settings.
  2. From the Secrets and variables dropdown click Actions.
  3. Click New repository secret.
  4. Paste the output of the command above in the Secret field.
  5. Name the secret FLY_API_TOKEN.
  6. Click Add secret.

Let's create the Github action! In your repository, create a new file in the .github/workflows folder and name it deploy.yaml:

name: Fly Deploy
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: Deploy app
    runs-on: ubuntu-latest
    concurrency: deploy-group
    steps:
      - uses: actions/checkout@v4
      - uses: superfly/flyctl-actions/setup-flyctl@master
      - run: flyctl deploy --remote-only
        working-directory: ./app
        env:
          FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

The action is fairly straightforward. It runs on every push to the main branch, and it checks out the code from the repository using the checkout action. Once the code is checked out, it runs the flyctl deploy command using the superfly/flyctl-actions/setup-flyctl action.

Note

Note that if you're app is in a subfolder, don't forget to set the working-directory to the correct path. In my case, I created the app in the ./app folder so I set the working-directory field to that value.

That's it! Now every push to the main branch triggers a deployment to Fly.io. If you had any database migrations or other setup steps, perhaps running unit tests you could see how the Github action could become a bit more complex. So before we get to that spot, I'll show you another tool that I looked at recently - Dagger.

Daggerize your scripts

Dagger is a tool that allows you to replace your scripts used for setup, building, testing, deploying and bunch of other things, with functions written in a programming language. In this article I'll use Go, but you can also write Dagger functions in Python and TypeScript.

One of the reasons why I typically reach for a shell script is that it's easy to write and I can run it right away. You can say writing simple script with Go/Python/TypeScript isn't that hard either, but I'd disagree. It's not the "writing the code" that makes it hard and tedious for me, it's more of the "setting up the environment" part and "download the tooling and dependencies" (make sure you get the right version!), packaging the "script" etc.

This is what Dagger made easy for me. I can bootstrap the Dagger function with a single command (dagger init), write the function and run it without ever downloading any dependencies or setting up the environment!

To run a Dagger function you can use call command in the Dager CLI (e.g. dagger call [function_name]). I am not going to write a walkthrough, because the quickstart in Dagger's docs is great, so check that out if you're interested.

Writing a Dagger function for Fly.io

Back to the Github action and the Fly.io stuff. Here's what I did in terms of writing a Dagger function to replace or rather abstract the flyctl CLI. The full source is here, but sinces there's not much to it, I'll paste the functions here as well:

// Deploy deploys an app from the src folder to Fly.io
func (m *Flyio) Deploy(ctx context.Context,
	// +required
	src *Directory,
	// +required
	token *Secret) (string, error) {
	return m.FlyContainer(ctx, token).
		WithMountedDirectory("/src", src).
		WithWorkdir("/src").
		WithExec([]string{"/root/.fly/bin/flyctl", "deploy"}).
		Stdout(ctx)
}

// FlyContainer creates a container with the flyctl CLI installed
func (m *Flyio) FlyContainer(ctx context.Context, token *Secret) *Container {
	return dag.Container().
		From("alpine:3.20.0").
		WithExec([]string{"apk", "add", "curl"}).
		WithExec([]string{"curl", "-LO", "https://fly.io/install.sh"}).
		WithExec([]string{"sh", "install.sh"}).
		WithSecretVariable("FLY_API_TOKEN", token)
}

The FlyContainer function creates (and returns) a new Docker container based on alpine:3.20.0, installs curl, downlads the script and installs the Fly.io CLI and then mounts an environment variable FLY_API_TOKEN from the secret passed to the function (Secret is a concept in Dagger that allows you to input things like API keys, passwords, into Dagger functions without exposing them in the logs, for example).

The Deploy function uses the FlyContainer function to create a container, mounts the source directory that was pass-in by the user, and then runs the flyctl deploy command in that mounted folder and pipes the output to standard out (stdout).

When I said you don't need to download any dependencies or set up the environment, I meant it. You can run the Dagger function right away, without any setup. The Dagger CLI acts as a CLI for these functions - for example, here's what happens if you list the functions in this module I created:

Name            Description
deploy          Deploy deploys an app from the src folder to Fly.io
fly-container   FlyContainer creates a container with the flyctl CLI installed

Then, to run the deploy function, you can do something like this:

dagger call deploy --src ./projects/htmx-todo/app --token=env:FLYIO_TOKEN

Note the --src flag is the path to the source code of the app you want to deploy, and the --token flag is the deployment token. It's prefixed with env which is telling Dagger to read the value from the environment variable. Other options here are file, to read the secret value from a file, or cmd to run a command and use the output as the value.

You might wonder -- well, this is all great, but how can I run this same Dagger function from my environment? Do I need to clone the repo, or what do I do? This is the REALLY COOL part - when calling dagger call or dagger functions you can point to the location of the Dagger module (e.g. the Github repo for example where I published the module) and it will work the same way as if you had the module locally:

$ dagger functions  -m github.com/peterj/dagger-modules/flyio@46138d1028f721d7a0cdc03794c64aa063584f46

```console
Name            Description
deploy          Deploy deploys an app from the src folder to Fly.io
fly-container   FlyContainer creates a container with the flyctl CLI installed

Putting it all together

The last step is to run the Dagger function in the Github action. There's a published Github Action for running Dagger and to use it you can modify your workflow, something like this:

name: Fly Deploy with Dagger
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: Deploy app
    runs-on: ubuntu-latest
    concurrency: deploy-group
    steps:
      - uses: actions/checkout@v4
      - uses: dagger/dagger-for-github@v5
        with:
          verb: call
          module: github.com/peterj/dagger-modules/flyio@46138d1028f721d7a0cdc03794c64aa063584f46
          args: deploy --src $PWD --token=env:${{ secrets.FLY_API_TOKEN }}

The Github action looks more or less the same, but now if your deployment "script" changes, you can update the Dagger function(s), easily test them locally (without setting up anything) and you can be confident that if it works locally, it will work in the CI as well.