Skip to content

Organize packages, inject credentials by environment

Your schema packages work. But now you’ve got three of them, three teams touching them, and eventually three different deploy pipelines pointing at dev, staging, and production. If the connection string is baked into the settings file, you’re writing three versions of that file per service. Per environment. You’re one copied credential away from a prod database getting a dev deploy.

Hey folks. I’m Forge Barrett, master of the Content Forge here at SchemaSmith.

This is Module 3 of Course 9. Two modules in, you know how native packages feel and why the DDL looks different per engine. Now we get into how you run three of these things as a real operation: how to organize a package so a human can find anything in ten seconds, and how to keep credentials entirely out of source control without giving up a single feature. These are the mechanics that make a polyglot fleet manageable. Let’s see them.

First, a framing point you’ll want to carry into every decision in this module.

In production, each service is its own repo. Orders on SQL Server — its own repo, its own quench.settings.json, its own deploy cadence, its own secrets rotation. Catalog on PostgreSQL — same deal. Sessions on MySQL — same deal. Three services, three repos, three independent release tracks. They share nothing except the shape of the tool that deploys them.

The lab co-locates all three services under one folder because that’s how lab bundles ship. Don’t read that as a recommendation. A real polyglot setup is a polyrepo, not a monorepo with three package subfolders.

Native + separate. Always. Each service lives on its own deployment timeline. The Orders team can ship a schema change on Tuesday without waiting for the Catalog team’s Friday release. That independence is the whole point.

SchemaSmith converges each database to exactly what its package declares, so every module stands alone. You don’t need prior modules deployed. Run this one cold and it reaches the right end state.

Subfolder organization: structure for humans, not for the engine

Section titled “Subfolder organization: structure for humans, not for the engine”

Open sqlserver/package/Templates/Main/Tables/ and you’ll see two folders:

sqlserver/package/Templates/Main/Tables/
├── Core/
│ ├── dbo.Customer.json
│ ├── dbo.SalesOrder.json
│ └── dbo.OrderItem.json
└── Reference/
└── dbo.OrderStatus.json

Core/ holds the service’s own operational tables — the rows that change when customers place orders. Reference/ holds lookup tables the service reads but doesn’t own — stable enumeration data like order statuses that drive FK validation and application logic.

The PostgreSQL catalog package follows the same split:

postgres/package/Templates/Main/Tables/
├── Core/
│ ├── public.category.json
│ └── public.product.json
└── Reference/
└── public.product_status.json

And MySQL sessions:

mysql/package/Templates/Main/Tables/
├── Core/
│ ├── Session.json
│ └── Event.json
└── Reference/
└── EventCategory.json

Same pattern, three engines, three conventions. Brackets and dbo. on SQL Server, public. prefix on PostgreSQL, backtick-quoted identifiers on MySQL.

SchemaSmith discovers table JSON recursively. It scans with SearchOption.AllDirectories, so Core/dbo.Customer.json and Reference/dbo.OrderStatus.json both get picked up in the same pass. The folder structure is for humans — deployment is unaffected. You could add a third subfolder, nest deeper, rename either folder, and SchemaSmith would find every table the same way.

What does that mean in practice? Artifact output lists all four SQL Server tables regardless of which subfolder they live in. The engine doesn’t know about Core/ and Reference/ — it knows about dbo.Customer, dbo.SalesOrder, dbo.OrderItem, and dbo.OrderStatus. The organization is yours. Use it to make the package readable at a glance.

File-less config: one settings file, every environment

Section titled “File-less config: one settings file, every environment”

Here’s the quench.settings.json for the SQL Server package:

{
"Target": { "Databases": ["orders"], "ConnectionProperties": { "TrustServerCertificate": "True" } },
"WhatIfONLY": false,
"SchemaPackagePath": "./package",
"KindleTheForge": true,
"ArtifactPath": "./artifacts",
"CheckpointDirectory": "./checkpoints"
}

No Server. No User. No Password. The file knows what database to target and where the package lives. It does not know who’s connecting or from where.

PostgreSQL:

{
"Target": { "Databases": ["catalog"], "ConnectionProperties": {} },
"WhatIfONLY": false,
"SchemaPackagePath": "./package",
"KindleTheForge": true,
"ArtifactPath": "./artifacts",
"CheckpointDirectory": "./checkpoints"
}

MySQL:

{
"Target": { "Databases": ["sessions"], "ConnectionProperties": {} },
"WhatIfONLY": false,
"SchemaPackagePath": "./package",
"KindleTheForge": true,
"ArtifactPath": "./artifacts",
"CheckpointDirectory": "./checkpoints"
}

Same shape, three files, zero credentials. These files are safe to commit. They’re safe to share. They don’t become different files per environment — they’re the same file in every environment.

The connection lands via SmithySettings_ environment variables. The prefix maps to config keys; __ nests — so SmithySettings_Target__Server sets Target:Server, and SmithySettings_Target__Password sets Target:Password. Env vars override the settings file. Set them in your shell before you run the deploy and SchemaQuench picks them up automatically.

SQL Server (orders) — macOS / Linux:

Terminal window
export SmithySettings_Target__Server="localhost,11433"
export SmithySettings_Target__User="sa"
export SmithySettings_Target__Password="Learn!Passw0rd"

SQL Server (orders) — Windows PowerShell:

Terminal window
$env:SmithySettings_Target__Server = "localhost,11433"
$env:SmithySettings_Target__User = "sa"
$env:SmithySettings_Target__Password = "Learn!Passw0rd"

PostgreSQL (catalog) — macOS / Linux:

Terminal window
export SmithySettings_Target__Server="localhost"
export SmithySettings_Target__Port="15432"
export SmithySettings_Target__User="postgres"
export SmithySettings_Target__Password="Learn!Passw0rd"

PostgreSQL (catalog) — Windows PowerShell:

Terminal window
$env:SmithySettings_Target__Server = "localhost"
$env:SmithySettings_Target__Port = "15432"
$env:SmithySettings_Target__User = "postgres"
$env:SmithySettings_Target__Password = "Learn!Passw0rd"

MySQL (sessions) — macOS / Linux:

Terminal window
export SmithySettings_Target__Server="localhost"
export SmithySettings_Target__Port="13306"
export SmithySettings_Target__User="root"
export SmithySettings_Target__Password="Learn!Passw0rd"

MySQL (sessions) — Windows PowerShell:

Terminal window
$env:SmithySettings_Target__Server = "localhost"
$env:SmithySettings_Target__Port = "13306"
$env:SmithySettings_Target__User = "root"
$env:SmithySettings_Target__Password = "Learn!Passw0rd"

With env vars set, each deploy is one command from the service’s directory:

macOS / Linux:

Terminal window
schemaquench --ConfigFile:quench.settings.json --LogPath:"$PWD/logs"

Windows PowerShell:

Terminal window
schemaquench --ConfigFile:quench.settings.json --LogPath:"$PWD\logs"

Three times — once from sqlserver/, once from postgres/, once from mysql/. Each exits 0. Each run picks up a different set of env vars and hits a different database. The settings file never changes.

Re-run any of them. Clean. SchemaSmith compares the declared state against the live database and finds nothing to do. No pending changes, no surprises. That’s convergence — and it holds across all three engines.

SQL Server confirmed four tables from both subfolders: dbo.Customer, dbo.SalesOrder, and dbo.OrderItem from Core/; dbo.OrderStatus from Reference/. PostgreSQL confirmed category and product from Core/, product_status from Reference/. MySQL confirmed Session and Event from Core/, EventCategory from Reference/. Three engines. Three exit 0s. Connection supplied entirely by env vars — nothing in the settings files.

That’s the pattern. One settings file per service. Zero credentials in source control. Point it at dev by setting dev env vars; point it at production by setting production secrets in your CI environment. Same command either way.


Three packages, one organizational convention, credentials nowhere near source control. A smith who stores the combination to the strongbox on the strongbox lid isn’t protecting anything. Keep secrets out of the files that get committed — inject them at deploy time, where the environment can guard them properly.

Subscribe and stick around. Next time, we’ll take these three organized, credential-free packages into a real CI pipeline — WhatIf gating, per-environment variable injection in GitHub Actions, and a per-service deploy workflow built straight on the structure you just put in place.

Until then, may your package lay open like a well-kept workshop — everything in its place, nothing left where it shouldn’t be.

— Forge

Check yourself: You add a third subfolder — `Tables/Audit/` — inside the SQL Server package to hold audit-trail tables. Does that change how SchemaSmith discovers and deploys those tables?

No. SchemaSmith discovers table JSON recursively using SearchOption.AllDirectories, so tables in Tables/Audit/ are found in the same pass as tables in Tables/Core/ and Tables/Reference/. The subfolder structure is for human organization only — deployment is unaffected. The artifact output will list every table regardless of which subfolder it lives in.

Check yourself: Your `quench.settings.json` files contain no `Server`, `User`, or `Password` keys. How does SchemaQuench know how to connect — and what does that mean for deploying the same package to dev, staging, and production?

Connection details are injected via SmithySettings_Target__* environment variables set before the deploy runs. The SmithySettings_ prefix maps to config keys, and __ nests them — so SmithySettings_Target__Server sets Target:Server, SmithySettings_Target__Password sets Target:Password, and so on. Env vars override the settings file at runtime. That means the same quench.settings.json deploys to every environment unchanged — only the env vars differ. No per-environment files, no credentials in source control, no file to update when a password rotates.