Using Cue for fly app configuration

I’ve been using https://cuelang.org/ a lot for my configuration needs. It’s still a young project but it’s pretty awesome. You can write partial schemas (with both concrete values and abstract constraints) and have cue do the checking and unification for you automatically.

For example, you might have an application:

  • expecting a volume to be mounted at a specific location, or
  • with specific constraints on runtime environment variables

So now instead of waiting for my application to crash sometime after I deploy it, I see an error message at deploy time (i.e. when I try to export the .cue files to JSON or TOML).

Over the years I’ve tried to solve this kind of problem with various combinations of sed, jq, kustomize, helm, raw string templating, and other even less appropriate tools. I’ve been very happy to replace them all with cue.

I went through the fly docs and put together the schema below. Also shared it in the cue playground: CUE Playground

Hope others find it helpful!

package fly_config

#portHandler: "tls" | "http"
#portValue: =~"^[0-9]+$"
#port: {
    handlers: [#portHandler, ...]
    port: #portValue
}
#duration: (string & =~"^[0-9]+s$") | int

#concurrency: {
  hard_limit: *25 | int
  soft_limit: *20 | int
}

#check: {
  grace_period: *"1s" | #duration
  interval: *"15s" | #duration
  timeout: *"2s" | #duration
}

#tcpCheck: {
  #check
  port: #portValue
  restart_limit: *6 | int
}

#httpCheck: {
  #check
  method: string
  path: string
  protocol: "http" | "https"
  tls_skip_verify: bool
  headers: {string}
}

#mounts: {
  source: string // The source is a volume name that this app should mount. Any volume with this name, in the same region as the app and that isn't already mounted, may be mounted. A volume of this name must exist in some region for the application to deploy.
  destination: string // The destination is directory where the source volume should be mounted on the running app.
}

#experimental: {
  private_network?: bool
}

#service: {
  concurrency: #concurrency
  internal_port: *8080 | int
  protocol: *"tcp" | "udp"
  ports: [#port, ...]
  tcp_checks: [#tcpCheck, ...]
  mounts?: #mounts
  experimental?: #experimental
}

#app: {
  app?: string
  kill_signal?: *"SIGINT" | "SIGTERM" | "SIGQUIT" | "SIGUSR1" | "SIGUSR2" | "SIGKILL" | "SIGSTOP"
  kill_timeout?: *5 | (<=86400 & int)

  services?: [#service, ...]
}

#app

PS Some folks in the cue community are experimenting with generating application source code (complete with types, where appropriate!) to work with cue-specified values at runtime. This has been very helpful in my projects, where I want to make sure the code I’m deploying is deployed with the configuration it expects.

3 Likes

This is extremely cool. Still digging into it.

Hey @ajbouh

This is really cool. I have also been playing with CueLang a bit, but nothing in production yet. I see endless possibilities for Cue.

  1. Convert all my golang projects to use Cue for configuration.
  2. I was just trying to work out a good way to refactor a large docker compose into smaller bits, so that i can configure it with CueLang.

Do you have any progress to report ?

There are very few situations where using cue has not been a joy. I still recommend it, though the ecosystem and tooling are still a bit young.

Do you have any specific questions I can answer?

1 Like

Do you have an example repo ?

I would like to try this out and extend it to support all the fly.io configurations options.

I am gedw99 on GitHub :slight_smile: my telegram is in the profile too.