- Docs
- Operate
- Migrate from v0.7 to v0.8
Migrate from v0.7 to v0.8
Move service config to environment variables, wire adapters with Provide, and run generated servers through serve.Main.
v0.8 changes how a project configures and boots. Service config is environment-only, adapters wire through typed container.Provide calls, zever new scaffolds a smaller tree, and generated servers delegate boot to serve.Main. This page maps every change to its replacement.
Service config is env-only
Section titled “Service config is env-only”zever.yaml, zever.yml, and zever.json are gone. config.Load is gone. config.FromEnv() plus config.Resolve(env) replace them. A legacy file in the working directory is ignored, and config.FromEnv prints a one-line warning naming the file.
Translate each adapter: and options: key to an uppercase env variable. Every service uses <SERVICE>_<FIELD>; named instances use <SERVICE>__<NAME>__<FIELD>.
| Service | Env prefix | Default adapter |
|---|---|---|
| ai | AI_ |
anthropic |
| analytics | ANALYTICS_ |
log |
| auth | AUTH_ |
jwt |
| billing | BILLING_ |
stub |
| cache | CACHE_ |
memory |
| cdn | CDN_ |
noop |
| crypto | CRYPTO_ |
local |
| db | DB_ |
sqlite |
| document | DOCUMENT_ |
local |
| eventbus | EVENTBUS_ |
memory |
| flag | FLAG_ |
static |
| geo | GEO_ |
static |
| i18n | I18N_ |
embed |
| idempotency | IDEMPOTENCY_ |
memory |
| lock | LOCK_ |
memory |
| log | LOG_ |
slog |
| mailer | MAILER_ |
log |
| media | MEDIA_ |
local |
| notification | NOTIFICATION_ |
log |
| observability | OBSERVABILITY_ |
stdout |
| outbox | OUTBOX_ |
db |
| password | PASSWORD_ |
argon2id |
| payment | PAYMENT_ |
stub |
| permission | PERMISSION_ |
noop |
| queue | QUEUE_ |
memory |
| ratelimit | RATELIMIT_ |
memory |
| resilience | RESILIENCE_ |
memory |
| router | ROUTER_ |
stdhttp |
| scheduler | SCHEDULER_ |
embedded |
| search | SEARCH_ |
db |
| secrets | SECRETS_ |
env |
| session | SESSION_ |
memory |
| storage | STORAGE_ |
local |
| tenant | TENANT_ |
single |
| vectorstore | VECTORSTORE_ |
db |
| webhook | WEBHOOK_ |
http |
| workflow | WORKFLOW_ |
memory |
Example:
db: adapter: postgres options: dsn: postgres://app:secret@db:5432/app max_conns: 20log: adapter: slogDB_ADAPTER=postgresDB_DSN=postgres://app:secret@db:5432/appDB_MAXCONNS=20LOG_ADAPTER=slogRules: adapter: becomes <SERVICE>_ADAPTER; each options: key becomes <SERVICE>_<KEY> uppercased; one nesting level flattens with a single underscore (jwt.secret under auth becomes AUTH_JWT_SECRET); durations are Go strings in env (5s, 1m), not integer nanoseconds. Keys with no string form (maps, lists) cannot move to env. Full mapping and defaults: Config reference.
Wire adapters with Provide
Section titled “Wire adapters with Provide”The container no longer resolves adapters through name registries. zever new scaffolds one Ref var plus one container.Provide entry per instance, and zever add <battery> inserts more. Adapters still expose a Register() for the core registry, but the container path is Provide.
package app
var DBRef = db.Ref("default")package app
var DB = db.DefaultRef()var Log = log.DefaultRef()
func Providers() []container.Provider { return []container.Provider{ container.Provide(DB, dbsqlite.New), container.Provide(Log, container.WrapNoErr(logslog.New)), }}Resolve through the container with container.Get, or keep using the accessors when the default instance is wired:
import ( "github.com/zenta-dev/zever/container" "github.com/zenta-dev/zever/core/db")
var DB = db.DefaultRef()
// Providers wires DB to the sqlite adapter; Get(c, DB) returns the *sql.DB handle.Provide1, Provide2, and Provide3 add typed dependencies. Options come from config.DefaultOptions overlaid with env, then container.WithOptions appliers run last:
return []container.Provider{ container.Provide(DB, dbsqlite.New), container.Provide(DB, dbsqlite.New, container.WithOptions(func(o *db.Options) { o.MaxConns = 25 })),}Graph validation runs at container.New: duplicate Refs, missing non-Optional dependencies, and cycles surface from container.Get with typed DuplicateRefError, NotWiredError, or DependencyCycleError. zever explain prints the wiring graph. See Container.
Scaffold flags
Section titled “Scaffold flags”zever new picks batteries with --with, not --profile. --profile still works as a deprecated alias that prints the equivalent --with form.
zever new hello --profile apizever new hello --with cache,queue=redis,auth--batteries and --adapters still work. zever new scaffolds the minimal tree (about eight files): go.mod, schema/app.zen, cmd/server/main.go, internal/app/wiring.go, internal/service/hello, .env, .env.example, and .gitignore. Recipes such as docker, ci, worker, seed, compose, and k8s are opt-in via zever add <recipe>.
Generated servers use serve.Main
Section titled “Generated servers use serve.Main”The generated cmd/server/main.go is thin. It parses -addr and -grpc-addr, then calls serve.Main with the app’s providers and modules. serve.Main owns config resolution, container construction, signal handling, and graceful shutdown.
func main() { // build container, resolve router, start http.Server, wait for signal}func main() { addr := flag.String("addr", "", "HTTP listen address (empty uses PORT, default :8080)") grpcAddr := flag.String("grpc-addr", "", "gRPC listen address (empty disables)") flag.Parse()
var opts []serve.Option if port, err := serverPort(*addr); err == nil && port != "" { opts = append(opts, serve.WithPort(port)) } if *grpcAddr != "" { opts = append(opts, serve.WithGRPCAddr(*grpcAddr)) }
if err := serve.Main(app.Providers(), app.Modules(), opts...); err != nil { fmt.Fprintln(os.Stderr, "server:", err) os.Exit(1) }}Listen addresses and runtime toggles come from env or options: PORT (default 8080), GRPC_ADDR (empty disables gRPC), PPROF_ADDR (loopback pprof, empty disables), GRPC_REFLECTION, and ZEVER_ENV. Security defaults stay fail-closed: reflection off unless opted in, TLS 1.2 floor, security headers, and refusal of the deterministic dev crypto key outside a dev ZEVER_ENV. See Serve module.
CLI surface
Section titled “CLI surface”workerandschedule runare the canonical commands.queue:workandschedule:runstill work as hidden aliases.doctorscopes its report to wired providers; it no longer takes--config.config showprints per-instance redacted values and no longer takes--config.zever explainprints the wiring graph when invoked bare, or one service’s wiring forzever explain db/zever explain db/analytics.zever add <battery>/<adapter>editsinternal/app/wiring.gowithgo/ast, appends to.env.example, and runsgo getplusgo mod tidy.
Project layout settings
Section titled “Project layout settings”Project layout is defaults-only, overridable by six ZEVER_* variables (no file, no --config flag):
| Variable | Overrides | Default |
|---|---|---|
ZEVER_SCHEMA_DIR |
.zen schema directory |
schema |
ZEVER_GENERATED_DIR |
generated-code directory | generated |
ZEVER_SERVER_ENTRY |
server entrypoint package | cmd/server |
ZEVER_WORKER_ENTRY |
worker entrypoint package | cmd/worker |
ZEVER_TINKER_ENTRY |
tinker shim entrypoint | cmd/tinker-shim |
ZEVER_SEED_ENTRY |
seed entrypoint package | db/seed |
Migration checklist
Section titled “Migration checklist”- Move every
zever.yamlstanza to.envor the process environment using the table above. - Delete
zever.yaml;config.FromEnvwarns if one remains in the working directory. - Replace
config.Load(...)withconfig.FromEnv()plusconfig.Resolve(env). - Replace adapter
Register()wiring withcontainer.Provideentries ininternal/app/wiring.go. - Replace
--profilewith--within scripts and CI. - Regenerate the server entrypoint so it calls
serve.Mainwith providers and modules. - Update Kubernetes manifests to inject
env:from a Secret (no mounted config file).