Maestro is a CLI tool that scaffolds scalable Go microservice projects. It follows an opinionated design pattern - go.work, per-service go.mod, Dockerfiles, docker-compose, hot reload, secrets, databases, and Taskfiles. Managing growing projects in Go can be tough, but with a single task up command you can get it up and running immediately.
Unlike a one-shot generator, maestro reconciles: project.toml is the source of truth, and maestro refresh brings every generated file back in line on demand - without clobbering your edits.
Built on top of the project.toml philosophy: one workspace, many services under it, each in its own module, all reconciled from a single project.toml
-
Bootstraps a workspace with
go.work, a sharedcommon/gomodule with handy starter code, dev and prod compose files, Dockerfiles, Air hot-reload,.gitignore, and a root Taskfile. -
Generates services in four shapes (
bare,http,grpc,worker), each with its owncmd/, internals, Taskfile, and (optionally)sqlc/goosewiring for Postgres or SQLite — in Docker or against a live URL. -
Refreshes the project state via
refresh— changeproject.toml, run one command, and all generated files (compose, Taskfile, go.mod replace directives) catch up. User edits are preserved. -
Stays out of your secrets. Doppler is the only built-in provider. Maestro writes
doppler.yamland reads viadoppler secrets download. It never creates projects or pushes secrets.
The quickest path is go install. Prebuilt binaries are on the releases page for machines without a Go toolchain.
Download a prebuilt binary straight from the releases page, or fetch it with gh:
Linux / macOS
gh release download --repo Zagforge-Org/maestro \
--pattern '*Linux_x86_64.tar.gz' # or *Darwin_arm64.tar.gz, etc.
tar xzf maestro_*.tar.gz
sudo mv maestro /usr/local/bin/
maestro versionWindows (PowerShell)
gh release download --repo Zagforge-Org/maestro --pattern '*Windows_x86_64.zip'
Expand-Archive maestro_*.zip -DestinationPath .
# Drop maestro.exe somewhere on PATH (create the dir + add to PATH if needed):
Move-Item maestro.exe "$Env:LOCALAPPDATA\Programs\maestro\maestro.exe"
maestro versionAvailable archives per release: Linux_x86_64, Linux_arm64, Darwin_x86_64, Darwin_arm64, Windows_x86_64, Windows_arm64.
Requires a Go toolchain:
go install github.com/Zagforge-Org/maestro@latestmkdir my-project && cd my-project
maestro init # interactive: name, repo owner, optional Doppler project
maestro service api # interactive: shape (bare/http/grpc/worker), optional DB
task api:dev # hot-reloaded local dev for that one service
task up # bring every service up via docker-composeTo check that all required tools are installed:
maestro doctorBoth init and service launch a TUI wizard by default. Pass -y (or --yes) and the flag values become the source of truth - handy for CI, setup scripts, and reproducible playbooks.
# Scaffold a project without touching the wizard
maestro init -y \
--name my-project \
--repo-owner yourname \
--doppler-project my-project \
--ci --license
# Generate an HTTP service with Postgres in Docker
maestro service api -y --shape http --db postgres
# Generate a worker that talks to an existing Postgres (the URL is pinged, not stored)
maestro service ingest -y \
--shape worker --db postgres --db-mode live \
--db-url "postgres://user:pass@host:5432/dbname"Required with -y: --name and --repo-owner for init; --shape for service. Everything else has a sensible default - port auto-assigns to the next free slot in the shape's range, --db defaults to none, --db-mode defaults to docker. Live URLs are pinged before scaffolding; a failed ping is a hard error.
| Command | Purpose |
|---|---|
maestro init |
Scaffold a new workspace (interactive wizard). |
maestro service <name> |
Generate a new service (interactive shape + DB picker). |
maestro preset apply <preset> <service> |
Scaffold a curated feature (e.g. auth-jwt, api-gateway) and deposit its reusable libs into common/go. See docs/PRESETS.md. |
maestro preset list |
List the available presets. |
maestro secrets keygen |
Print a dev ES256 keypair + app secrets to load into a provider or .secrets/. |
maestro refresh |
Reconcile generated files with project.toml. Preserves user edits. |
maestro rename <old> <new> |
Rename a service across the workspace — directory, module path, sibling imports, Dockerfiles, Air configs, project.toml, go.work. Reverse with maestro rename <new> <old>. |
maestro delete <name> |
Remove a service — its directory, its go.work entry, its compose entries, its project.toml section. |
maestro doctor |
Verify that git, task, go, docker, air (and sqlc/goose/buf when relevant) are on PATH. |
After maestro init + maestro service api --shape=http:
my-project/
├── project.toml # source of truth for `refresh`
├── go.work # multi-module workspace
├── docker-compose.yaml # prod compose, regenerated by refresh
├── docker-compose.dev.yaml # dev compose with bind-mounts + Air
├── Taskfile.yaml # root Taskfile, regenerated by refresh
├── Taskfile.local.yaml # optional, user-owned, never touched
├── .gitignore # includes .secrets/, tmp/, etc.
├── common/go/ # shared module — imports resolve here under Docker too
└── api/
├── cmd/api/main.go
├── internal/server/ # Gin + zap, /health + /ready
├── docker/
│ ├── dev.Dockerfile
│ └── prod.Dockerfile
├── go.mod # with replace ../common/go
└── Taskfile.yaml # api:dev, api:up, api:down, api:proto:generate, …
Add --shape=grpc and you also get buf.yaml, buf.gen.yaml, and proto/<ident>/v1/<ident>.proto in a layout that satisfies buf lint (PACKAGE_DIRECTORY_MATCH).
Add a database and you also get sqlc.yaml, internal/db/ (with a hand-authored conn.go exposing db.Open(ctx, url); sqlc's generated code lands alongside it as db.go + models.go + queries.sql.go when you run task <svc>:generate), internal/migrate/, and - if you chose Docker mode - a <name>-db sidecar with a managed DATABASE_URL in the compose env.
The DB workflow is migrate-first: sqlc reads its schema from internal/migrate/, so the cycle is task <svc>:migrate:create -- <name> → write your CREATE TABLE … into the generated file → task <svc>:migrate:up → write queries into internal/db/queries.sql → task <svc>:generate. Writing a query against a table that has no migration is the most common cause of a confusing sqlc generate error.
Every file in a scaffolded project falls into one of three categories - managed (regenerated by refresh), user-owned (scaffolded once, then yours), or project.toml (the source of truth you edit by hand). The full inventory lives in docs/MANAGED_FILES.md.
| Shape | Generated |
|---|---|
bare |
cmd/<svc>/main.go printing "Hello". |
http |
Gin server with /health, /ready, structured request logging via zap, graceful shutdown. |
grpc |
grpc.NewServer() with health + reflection, buf-managed proto layout, signal-driven GracefulStop. |
worker |
Long-running loop over ctx.Done() + ticker, exits cleanly on SIGINT/SIGTERM. |
All shapes use zap.NewProduction() with ReplaceGlobals so libraries can log via zap.L().
Shapes are empty skeletons; presets are working features scaffolded on top.
Applying one (maestro preset apply <preset> <service>) generates the feature's
services and deposits the reusable libraries it's built from into your
common/go, so you build your own services on those building blocks rather than
being locked into the preset's code.
| Preset | What it scaffolds |
|---|---|
auth-jwt |
JWT auth service — email-OTP verify, password + Google sign-in, MFA, MFA-gated staff roles with RBAC, refresh rotation — plus a cleanup worker. |
api-gateway |
Stateless edge — reverse proxy, edge JWT validation, signed gateway-to-service identity via internalauth, Redis rate limiting, CORS, API versioning. |
The end-to-end walkthrough — standing up auth behind the gateway, running it out
of the box, and writing your own service that reads the gateway-injected caller
via the identity lib — is in docs/PRESETS.md.
[project]
name = "my-project"
repo_owner = "yourname"
[dev]
containerized = true # generate Dockerfiles + compose entries
hot_reload = true # generate .air.toml + Air step in dev compose
[secrets]
provider = "doppler" # or omit / "none"
doppler_project = "my-project"
[service]
[service.api]
shape = "http"
db = "postgres"
db_mode = "docker"
[service.worker]
shape = "worker"Edit it, run maestro refresh, and every managed file catches up. The wizards write this file for you; or you can also edit it by hand.
maestro doctor will tell you what's missing. The full set:
- Required: Git, Task, Go, Docker, Air
- For DB services: sqlc, goose
- For gRPC services: buf
Pre-1.0 and MIT licensed. The core flow (init → service → refresh → up) is solid and covered by both unit tests and end-to-end smoke tests (go test -tags=e2e ./...), including a refresh-idempotency invariant that fails if a second refresh against a freshly-scaffolded project produces any diff at all. Per-service Dockerfiles are scaffolded once and treated as user-owned thereafter - refresh warns when they no longer reflect [dev] hot_reload or [service.<name>] db but never rewrites them; regenerating is maestro delete <svc> && maestro service <svc>. maestro rename is its own inverse - a partial run is reversed by running it backwards. Pieces still on the roadmap:
- Partial-failure rollback in
serviceif a downstream step bombs mid-scaffold
MIT — see LICENSE. Copyright © 2026 Anze and Zagforge.

