ultra

package module
v0.0.30 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 6 Imported by: 0

README

ultra

Ultra

CI Go Reference


Ultra provides an opinionated and declarative framework for configuring Go apps with Docker.

Most configuration tools answer one question: "How do I load configuration?"

Ultra answers a different question: "How do I validate that my application has the configuration it needs before it starts?"

With ultra, you can inject, parse, and validate secrets and configuration for each containerized application without having to re-define those secrets repeatedly. Define your app's configuration once, and let Ultra do the rest.

NOTE: This project is still under development. Expect breaking changes!

ultra demo

Quickstart

  1. Install the CLI:
go install github.com/harrisoncramer/ultra/cmd/ultra@latest
  1. Create a config package for each application using ultra:
package config

type Config struct {
	DatabaseURL string `env:"DATABASE_URL" secret:"true" required:"*"`
	LogLevel    string `env:"LOG_LEVEL" envDefault:"info"`
}

Load it once at startup with ultra.Load, passing a pointer to your config; it parses the environment into it and hands it back:

cfg, err := ultra.Load(&config.Config{})
if err != nil {
    log.Fatal(err)
}
  1. Validate the configuration and/or run your command with secrets injected:
ultra validate apps/worker --secret-resolver 1password --vault Engineering                     # fails fast if a required value is missing, or malformed
ultra run apps/worker --secret-resolver 1password --vault Engineering -- docker compose up     # resolves secrets, injects DATABASE_URL, starts the container
  1. Optional: add an .ultra.toml at the repo root, naming your apps and secret store, so you can drop the flags:
apps = ["apps/worker"]

[secrets]
resolver = "1password"

[secrets.1password]
vault = "Engineering"

You can then just run:

ultra validate
ultra run -- docker compose up

Why ultra

A containerized Go app needs its configuration as environment variables. The usual way to get them there is a docker-compose service that enumerates every variable:

# docker-compose.yml
services:
  worker:
    build: .
    environment:
      LOG_LEVEL: info
      DATABASE_URL: ${DATABASE_URL}
      STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
      GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
      GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}

Then a secret file:

LOG_LEVEL=info
DATABASE_URL=postgres://user:pass@db:5432/app
STRIPE_SECRET_KEY=sk_live_...
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...

This works, but it has drawbacks.

First, every variable is declared in three places: the struct your app parses (or os.Getenv calls), the Docker compose environment: block, and the .env file or environment system. Adding a secret means editing all three, and over time they drift out of sync.

Second, the secrets often leak to disk. An .env file may hold plaintext credentials, which leak through backups, accidental commits, or shell history.

Lastly, nothing is validated until the container boots. Missing or malformed values can slip through development processes, and surface as runtime crash rather than an error at boot.

Ultra removes the duplication and the disk. Your typed config is the single source of truth, and fields tagged secret:"true" are the secrets.

Define your configuration as normal Go structs and annotate each field with where its value comes from: an environment variable, a secret provider, and so on. During development and in CI, Ultra resolves those values from providers like 1Password or AWS Secrets Manager and validates the result against the same struct, including its validation tags, so missing or malformed configuration fails before your application ever starts.

type Config struct {
	LogLevel    string `env:"LOG_LEVEL" envDefault:"info"`
	DatabaseURL string `env:"DATABASE_URL" secret:"true" required:"*"`
	StripeKey   string `env:"STRIPE_SECRET_KEY" secret:"true" required:"*"`
}

Ultra reads that struct, resolves the secret-tagged values from your store (1Password, AWS Secrets Manager, Vault) entirely in memory, and injects them into the container at run time. Nothing is written to disk. The compose file keeps only the non-secret config, stated explicitly:

# docker-compose.yml
services:
  worker:
    build: .
    environment:
      LOG_LEVEL: info

Ultra is also capable via ultra validate of checking whether every app's config is valid against the full environment it would boot with, so a missing secret or an unparseable value fails fast instead of at container start.

Configuration is a contract. Your app likely already has an implicit config schema, spread across its structs, os.Getenv calls, and deployment files. Ultra makes that schema explicit. The ultra validate command resolves every dependency and checks every field against the struct, so it is closer to schema validation than to loading.

That said, Ultra is deliberately narrow. It does not try to be Kubernetes, Terraform, or your cloud platform, and it does not want to generate your production infrastructure. Those systems own networking, IAM, secret rotation, provisioning, and deployment strategy, and they are good at it. Ultra does not replace runtime loading in production. It owns one thing: the contract between an application and the configuration it requires. Protocol Buffers define an API without dictating how it is served. OpenAPI describes a service without running it. Database schemas describe the shape of data without choosing the storage engine. Ultra applies that idea to application configuration: describe the requirements, validate them everywhere, and leave fulfillment to the systems built for it.

Documentation

Concepts and guides:

Command reference, with the usage line and every flag for each command:

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Load

func Load[T any](cfg *T, opts ...Option) (*T, error)

Load parses the environment into cfg and returns it, erroring if anything required is missing or malformed. Apps depend on this instead of the env library directly, so config parsing, and the underlying dependency, is controlled in one place. Fields tagged `secret:"true"` are read here like any other.

Required-ness is declared with the required tag, not the env library's required/notEmpty options (Load rejects those). A field tagged `required:"*"` must be set and non-empty in every environment; `required:"a,b"` only in environments a or b, named via WithEnvironment; and an untagged field is never required. So one Config describes every environment and each enforces only its own required fields.

Types

type Option

type Option func(*loadOptions)

Option configures Load.

func WithEnvironment

func WithEnvironment(environment string) Option

WithEnvironment declares the environment cfg is being loaded for. A field tagged `required:"a,b"` is enforced only when environment is a or b; a field tagged `required:"*"` is enforced in every environment, including when no environment is given.

Directories

Path Synopsis
cli
Package cli builds ultra's command tree and wires the domain services together.
Package cli builds ultra's command tree and wires the domain services together.
cmd
gen-docs command
Command gen-docs writes the ultra command reference as one markdown file per command into the target directory (docs/reference by default).
Command gen-docs writes the ultra command reference as one markdown file per command into the target directory (docs/reference by default).
ultra command
internal
appcheck
Package appcheck holds the per-app static check shared by the lint and validate domains.
Package appcheck holds the per-app static check shared by the lint and validate domains.
compose
Package compose builds the single docker-compose override that forwards every app's resolved secrets into its container through app-namespaced launcher variables.
Package compose builds the single docker-compose override that forwards every app's resolved secrets into its container through app-namespaced launcher variables.
configreader
Package configreader is the shared source of truth for which secrets each app declares: it reads apps' Config packages and reports their secret env-var names.
Package configreader is the shared source of truth for which secrets each app declares: it reads apps' Config packages and reports their secret env-var names.
gen
Package gen is the generate domain: it writes a single names-only docker compose file covering every app, from the secret names each app's Config declares (read through the shared configreader).
Package gen is the generate domain: it writes a single names-only docker compose file covering every app, from the secret names each app's Config declares (read through the shared configreader).
lint
Package lint is the lint domain: it statically checks that every required key an app declares will be provided, without constructing or parsing any value, so it runs where the real secret values aren't reachable.
Package lint is the lint domain: it statically checks that every required key an app declares will be provided, without constructing or parsing any value, so it runs where the real secret values aren't reachable.
project
Package project models the repo layout the commands operate on: the root the compose file and app paths are anchored to, and where each app's config package lives.
Package project models the repo layout the commands operate on: the root the compose file and app paths are anchored to, and where each app's config package lives.
resolve
Package resolve defines ultra's secret and config resolver interfaces, the registry backends register into, and the layering that lets an override resolver win over a base one.
Package resolve defines ultra's secret and config resolver interfaces, the registry backends register into, and the layering that lets an override resolver win over a base one.
run
Package run is the run domain: it generates the combined names-only compose override into an ephemeral location, resolves each app's secrets, and execs a command with the launcher environment and COMPOSE_FILE set.
Package run is the run domain: it generates the combined names-only compose override into an ephemeral location, resolves each app's secrets, and execs a command with the launcher environment and COMPOSE_FILE set.
scan
Package scan is the config-scanning domain: it reads an app's exported Config struct and reports the env-var fields it declares.
Package scan is the config-scanning domain: it reads an app's exported Config struct and reports the env-var fields it declares.
validate
Package validate is the validate domain: it reconstructs the environment each app would boot with (its non-secret config plus its resolved scan) and loads the app's Config against it to check it parses.
Package validate is the validate domain: it reconstructs the environment each app would boot with (its non-secret config plus its resolved scan) and loads the app's Config against it to check it parses.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL