Home / Docs / Self-hosting & first steps
Self-hosting & first steps
From zero to a control plane running on your infra — in one command. yggdrasil init brings up Postgres, the core engine and adapters, creates the first admin and saves a CLI context. You walk away with a live, already-authenticated stack, ready to apply manifests and run workflows.
Requirements
Standalone mode packages everything into containers, so the list is short:
PATH. init aborts early with a clear message if it can't find the docker binary.docker compose plugin (v2, with a space — not the legacy docker-compose). init validates it by running docker compose version before anything else.yggdrasil CLI~/.kube/config read-only. You only need it if you'll run workflows that apply K8s objects.For existing cluster mode (--server), you need no local Docker at all — just network access to a core engine that's already running.
Install the CLI
The CLI ships as a pre-built binary on GitHub Releases. There's no container image: it's a single static binary per OS/architecture. The files follow the yggdrasil_<version>_<os>_<arch>.tar.gz pattern (Windows ships as .zip), with os and arch lowercase.
# pick the release and your OS/arch (lowercase)
$ VERSION=0.1.0 # the release you want
$ OS=darwin # darwin | linux | windows
$ ARCH=arm64 # amd64 | arm64
$ curl -L -o yggdrasil.tar.gz \
"https://github.com/dakasa-yggdrasil/yggdrasil/releases/download/v${VERSION}/yggdrasil_${VERSION}_${OS}_${ARCH}.tar.gz"
$ tar xzf yggdrasil.tar.gz
$ sudo mv yggdrasil /usr/local/bin/
$ yggdrasil version
✓ yggdrasil v0.1.0
Prefer to build from source? go install github.com/dakasa-yggdrasil/yggdrasil/cmd/yggdrasil@latest (Go 1.25+) works too. And a release binary knows how to update itself: yggdrasil update downloads, verifies the goreleaser checksum and swaps the binary in place; yggdrasil update --check only reports whether a new version is available.
Bring up standalone
With Docker and the CLI in place, one command stands up the entire control plane:
$ yggdrasil init
writing docker-compose.yml + .env to ./yggdrasil
docker compose up -d …
waiting for /readyz …
✓ yggdrasil is ready
directory: /abs/path/yggdrasil
server: http://localhost:9080
context: local
username: admin
password: k3p…q9 (generated — save it now)
Under the hood, init writes a docker-compose.yml and a .env (with random passwords) to the target directory — ./yggdrasil by default, adjustable with --dir — runs docker compose up -d, polls /readyz (which only responds once the database is reachable and bootstrap has finished), logs in as admin and persists the context to ~/.yggdrasil/config.yaml. The admin password is randomly generated and printed exactly once at the end — write it down. (Use --admin-username / --admin-password to pin credentials.)
The stack it brings up:
| Service | Role | Port |
|---|---|---|
postgres | Core state (versioned manifests, runs, identity) | internal :5432 |
core | The core engine — server with authority, HTTP API /api/v1 | :9080 (host) |
integration-kubernetes | K8s adapter (applies objects to your cluster) | adapter :8081 · health :8080 |
integration-schema-migrations | SQL migrations adapter (goose-postgres provider) | adapter :8082 · health :8080 |
Only the core port (9080) is published to the host by default; it's configurable with --port. Confirm everything with yggdrasil status.
Connect to an existing control plane
If you already brought up the core engine yourself — Helm, Kustomize or any other path — skip the whole compose flow and just register the context. init --server only logs in and persists the configuration:
# attach to an already-running control plane (no local Docker)
$ yggdrasil init --server https://cp.example.com
# or authenticate an account at any time (supports MFA)
$ yggdrasil login --server https://cp.example.com --username ops@example.com
Authenticator code (TOTP): ••••••
✓ logged in as ops@example.com → context "cp-example-com" saved
The context name is derived from the server host (so it won't overwrite the standalone local one), but you can pin it with --context. For accounts with MFA, login prompts for the authenticator code automatically in an interactive terminal; in CI, pass --totp <code> or --recovery-code <code>. The config.yaml is multi-context, kubectl-style: switch the active one with the YGGDRASIL_CONTEXT variable.
Transport: HTTP by default, AMQP opt-in
The core and the adapters talk over a transport. HTTP is the default and is always available — there's nothing to configure. RabbitMQ/AMQP is opt-in: the compose setup declares RabbitMQ under the amqp profile, so it only comes up when you explicitly ask for it.
# 1. in the generated .env, uncomment the credentials and broker URL
# BROKER_URL=amqp://yggdrasil:…@rabbitmq:5672/
# 2. bring it up with the amqp profile active
$ docker compose --profile amqp up -d
With BROKER_URL empty, the core comes up in HTTP-only mode and the RabbitMQ service is never started. You only turn AMQP on when your dispatch volume justifies the broker.
First workflow
With the stack up and the context saved, the cycle is always apply (creates a new version of the manifest) followed by run (dispatches the execution). run is synchronous by default and prints progress step by step:
$ yggdrasil apply -f my-workflow.yaml
✓ created workflow default/my-workflow (version 1)
$ yggdrasil run my-workflow -n default --input target=prod
workflow status: succeeded
✓ resolve-config succeeded
✓ apply-objects succeeded (kubernetes.apply)
✓ verify succeeded
Pass --input k=v as many times as you need; the values go through as strings and the workflow schema coerces the type on the server. Want to just see the diff before applying? yggdrasil apply -f … --dry-run. Want async dispatch? yggdrasil run … --async returns a run id to follow with yggdrasil logs and yggdrasil ops get.
Deploy the control plane
There's a dedicated shortcut to promote the control plane itself: yggdrasil deploy control-plane applies a control_plane manifest and then dispatches the seeded yggdrasil-deploy-control-plane workflow against it — syntactic sugar over apply + run that already encodes the Kubernetes and schema-migrations instances of the bootstrap topology.
$ yggdrasil deploy control-plane -f cp.yaml
✓ applied control_plane global/yggdrasil
→ dispatching workflow yggdrasil-deploy-control-plane
workflow status: succeeded
Use --no-run to apply the manifest without firing the workflow, and -f - to read from stdin. The instances used by the deploy can be overridden with --kubernetes-instance and --schema-migrations-instance.