Dev, Staging, Prod: One Config File, No Copy-Paste

Introduction

Dev, staging, and prod settings have a way of sprawling — the same host, port, and database name copy-pasted across a dozen scripts, drifting apart until a deploy points at the wrong database. This post keeps every environment in one config.yaml and selects it at runtime with --config, so a single command behaves correctly everywhere with no duplication.

Install the package

aux4 aux4 pkger install aux4/config

Step 1: one file, one section per environment

Everything lives under a top-level config: key, with a named section per environment. Create config.yaml:

config:
  dev:
    host: localhost
    port: 3000
    database: app_dev
  staging:
    host: staging.internal
    port: 3000
    database: app_staging
  prod:
    host: db.example.com
    port: 5432
    database: app_prod

Step 2: read values

aux4 config get reads a whole section as JSON, or a single value by path (nested paths use /):

aux4 config get --file config.yaml dev
{ "host": "localhost", "port": 3000, "database": "app_dev" }
aux4 config get --file config.yaml prod/port
5432

aux4 config set --file config.yaml --name staging/host --value staging.example.com writes a value back (creating nested paths as needed).

Step 3: the payoff — one command, every environment

Here's where it pays off. When a command runs with --configFile and --config, aux4 loads that section and auto-fills any command variable whose name matches a key — no glue code. Create .aux4:

{
  "profiles": [
    {
      "name": "main",
      "commands": [
        {
          "name": "connect",
          "execute": [
            "log:connecting to ${database} at ${host}:${port}"
          ],
          "help": {
            "text": "Connect to a database server",
            "variables": [
              {
                "name": "host",
                "text": "The server host"
              },
              {
                "name": "port",
                "text": "The server port"
              },
              {
                "name": "database",
                "text": "The database name"
              }
            ]
          }
        }
      ]
    }
  ]
}

The command declares host, port, and database but sets none of them. --config picks the section that fills them:

aux4 connect --configFile config.yaml --config dev
connecting to app_dev at localhost:3000
aux4 connect --configFile config.yaml --config prod
connecting to app_prod at db.example.com:5432

Same command, same variables — the environment is the only thing that changes. There's no if dev … elif prod anywhere; --config prod simply hands the command prod's values.

Step 4: shared defaults, defined once

Settings common to every environment shouldn't be repeated three times. Put them in a defaults.json:

{ "sslmode": "require", "poolSize": 10 }

Then merge them into each section (config merge reads JSON on stdin; --save writes the result back):

cat defaults.json | aux4 config merge --file config.yaml --name dev --save
cat defaults.json | aux4 config merge --file config.yaml --name staging --save
cat defaults.json | aux4 config merge --file config.yaml --name prod --save

Add ${sslmode} and ${poolSize} (with matching variables) to the connect command, and every environment now carries the shared defaults on top of its own values:

aux4 connect --configFile config.yaml --config prod
connecting to app_prod at db.example.com:5432 (sslmode=require, pool=10)

Change a default once and re-merge — every environment stays in sync.

Conclusion

aux4/config collapses your environment sprawl into one file: sections for dev, staging, and prod, selected with --config, feeding a command's variables automatically. No copy-paste, no environment branching, and no more deploys pointed at the wrong database — just one config, everywhere.

See Also