maxicfg

Configuration

maxicfg looks for a file named .maxicfg.yaml in the current directory and walks up the tree until it finds one. Everything in it is optional — the tool works with command-line flags alone.

Project file

# .maxicfg.yaml
version: 1

schema: ./config/schema.yaml

profiles:
  local:
    sources: [config/base.yaml, config/local.yaml]
  staging:
    sources: [config/base.yaml, config/staging.yaml]
  production:
    sources: [config/base.yaml, config/production.yaml]

lint:
  strict: true            # unknown keys become errors
  max_depth: 8

scan:
  ignore:
    - "config/fixtures/**"
    - "**/*.example.yaml"

Sources within a profile are merged left to right: later files override earlier ones. This is the usual "base plus overlay" arrangement, and it means the schema only has to describe the merged result.

Schema syntax

A schema is a YAML file with a single top-level keys mapping. Keys are written in dotted form regardless of the nesting in the source file.

keys:
  # a required string
  service.name:
    type: string
    required: true
    pattern: "^[a-z][a-z0-9-]{2,40}$"

  # an integer with bounds and a default
  server.port:
    type: int
    default: 8080
    min: 1
    max: 65535

  # one of a fixed set of values
  logging.level:
    type: enum
    values: [debug, info, warn, error]
    default: info

  # a duration, parsed with Go syntax
  cache.ttl:
    type: duration
    default: 5m

  # wildcards match any key at that position
  features.*:
    type: bool
    default: false

  # nested wildcards work too
  databases.*.host:
    type: string
    required: true

Supported types

TypeAcceptsExtra options
stringany scalarpattern, min_len, max_len
intintegersmin, max
floatnumbersmin, max
booltrue, false, yes, no, 1, 0
enumvalues from a listvalues (required)
duration30s, 5m, 2h30mmin, max
bytes512kb, 4mb, 1gbmin, max
urlabsolute URLsschemes
listsequencesof, min_items, max_items

Templating

maxicfg render substitutes variables into a template file. The syntax is intentionally minimal: only variable substitution and defaults, no loops or conditionals.

# config/production.yaml.tmpl
database:
  host: ${DB_HOST}
  port: ${DB_PORT:-5432}
  pool_size: ${DB_POOL:-40}

logging:
  level: ${LOG_LEVEL:-info}
$ DB_HOST=db.internal maxicfg render config/production.yaml.tmpl \
    --out config/production.yaml

A variable with no value and no default is an error, not an empty string. This is deliberate — silently rendering an empty host is worse than failing the build.

Ignoring keys

Some keys are expected to differ between environments and should not appear in a diff. Mark them in the schema rather than filtering after the fact:

keys:
  database.password:
    type: string
    required: true
    secret: true          # never printed, excluded from diff output

  deploy.replicas:
    type: int
    environment_specific: true   # excluded from drift reports

Environment variables

VariableEffect
MAXICFG_CONFIGpath to the project file, overrides discovery
MAXICFG_PROFILEdefault profile when --profile is omitted
MAXICFG_NO_COLORdisable coloured output
MAXICFG_LOGdebug enables verbose internal logging