Tech Series: DevOps and environments

Part 6 of the inveazy tech series. How the same product moves from laptop Docker SQL to shared LAN catalogs to Azure customer sites — without treating any of those databases as disposable. CI/CD, config layers, and release trains keep the shape constant.

TLDR: Three rules. (1) Same product everywhere. (2) Different config per environment. (3) Never mix local disposal with shared or customer databases. This post walks the dacpac workflow, CI/CD release train, and config layers that enforce it.

Part 6 of the inveazy tech series. This post is how the same product moves from laptop Docker SQL to shared catalogs to Azure customer sites — with CI/CD pipelines, config layers, and release trains — without treating any of those databases as disposable.

Part 5 locked the workspace wall: every hub query already knows its TenantId. Here we leave the query and follow the install — where it runs, what may be destroyed, and what must never be casualized.

Why this matters: Engineers ship the same product to three places. One mistake in environment discipline can wipe customer data that isolation alone cannot bring back.

Series:Multi-tenant design in practice · Earlier: Dacpac publish and data lockdown · Next → Provisioning a customer site


The rule: Same product shape everywhere. Different config. Different seed posture. Never confuse a local playground with a customer database.

Environment roadmap (same product, different honesty about data)

  [ Local ]              [ Shared / lab ]           [ Azure ]
  Docker SQL             LAN / demo catalogs        Dedicated install
  seed often ON          lockdown (seed OFF)        paywall provision
       |                        |                         |
       +-------- dacpac + web image family ---------------+
                         config identity changes
                         at each stop

Operators feel one product. Engineers feel three environments that must stay honest about what they are allowed to destroy.

Vocabulary: Install = one customer site + database. Workspace = operating unit inside that install. Lockdown = publish without re-seeding disposable sample story data. dacpac = compiled SQL Server database deployment package (the schema unit from the database project — see Part 2). SandBox = the practice workspace seeded for first-time setup when sample data is on.

Environment discipline: the foundation for scaling

Environment discipline is what lets the same dedicated-install product grow without treating every catalog as disposable. Feature work is easy to celebrate; this section is the foundation that celebration sits on.

inveazy is designed as a dedicated install per customer on the managed Azure path: its own application runtime, its own SQL database, its own hostname. That commercial shape only works if local development, lab stacks, and production-like catalogs are not casually interchangeable.

Part 2 already named the lockdown idea: schema publish is a release; sample CRM and inventory story data is a gated switch, not a side effect of every republish. For instance, when you republish to a shared lab database, the schema updates; the sales pipeline and inventory seed data do not load unless an operator explicitly enables them. This chapter is where that idea meets Docker on a laptop, a shared LAN database people actually use, and the Azure site that appears after checkout.

The risk: confusing playgrounds with production

The failure mode is rarely “Docker broke production.” It is habit transfer: a developer who casually resets a shared LAN SQL box is importing a local-playground mindset into a production-like environment. Docker is neutral; discipline is not.

Part 5’s isolation story assumes customer data is still there tomorrow — locked in by the schema and the workspace wall. DevOps has to assume the same, because one careless reset script aimed at the wrong catalog can wipe a year of customer records in seconds. Confusing those two postures is how “I just needed a column” becomes “why did SandBox come back.”

Local development: Docker SQL and the dacpac workflow

Local is where schema changes are born — and where disposal is allowed. The database project compiles to a dacpac — the same unit Azure will eventually receive.

A Docker SQL container gives you a disposable engine without pretending the data inside it is sacred. You can reset it. You should not import that attitude into a shared catalog or a paid customer database.

Day-to-day work looks like: change the model in the database project, rebuild, publish to local SQL, run the web app against that connection. The app does not invent tables on startup and does not run a second migration story that drifts from the dacpac. If the model and the database disagree, you fix the project and publish again — a missing column is a failed schema release, not a reason for the first HTTP request to own DDL. That is the same contract as Part 1 and Part 2.

Database project → build dacpac → publish to Docker SQL → run web against that DB

Local seed posture: when SandBox is allowed

Local publish often allows sample seed so a developer can click through CRM and inventory without inventing every row by hand. That is intentional — and it is the posture that must not travel unchanged to shared or customer catalogs.

The flag that turns sample story data on for a first install is the same family of switch that must stay off when you republish a shared or customer database.

Environment SeedSandboxSampleData Why
Laptop (Docker SQL) 1 (on) Developer convenience; data is disposable
Shared / lab catalogs 0 (off) Real users and story data; lockdown discipline
Azure customer install (day-2) 0 (off) Customer data safety on republish
Azure first install (day-0) 1 only when the product intends setup UX seed First-time SandBox for onboarding — not a day-2 habit
SeedSandboxSampleData = 1   // local / first-install convenience
SeedSandboxSampleData = 0   // lockdown — day-2 and shared catalogs

Shared catalogs and lab environments: production-like discipline

Once more than one person depends on a database, the catalog stops being disposable — even if it still lives on a LAN. Schema still moves by dacpac. Sample seed does not ride along for free.

Publish scripts for those targets should default toward lockdown: guards against accidental data loss, disable sample seed, and prevent “cleanup” shortcuts (like soft-deleting rows across tenants to mask a UI bug). A shared LAN database is closer to a customer install than to a laptop throwaway.

Why it matters: Habit transfer is the risk — not Docker itself. Treat shared and lab catalogs with production-like honesty about what you are allowed to destroy.

Azure dedicated installs: config, secrets, and scale

Azure is where the same product shape becomes a named customer footprint — install-specific config, secrets, and scale outside the developer’s laptop.

What the managed path looks like

On the managed product line, a paying customer receives a dedicated application runtime and a dedicated SQL database, reached at a hostname shaped like https://{slug}.inveazy.com. That install is not a row in a shared mega-tenant app server. It is a footprint the provision pipeline creates when checkout succeeds — the full engineering story is the next post in this series (Provisioning a customer site).

From a DevOps view, Azure is where config, secrets, and scale live outside the developer’s machine: connection strings, commerce keys for that install, public base URL, mail, and the switches that say this site is production-like. The shape of the product is still the dacpac and the same web image family you ran locally. The identity of the site is the config and the database contents.

Same web image + same dacpac family
  + install-specific config (URL, SQL, Stripe, mail)
  + dedicated database
→ customer site

Scale without rewriting the product

Dedicated SQL and a dedicated site runtime mean you can grow database and compute resources for one customer without rewriting multi-tenant routing. Commercially, growth also means more workspaces inside the install or another dedicated install for another brand — still the same release train. Private-network / self-hosted targets are the same product shape on customer-owned infrastructure when that path is published; until then, lab and Docker-style stacks are how private-style deploys get rehearsed.

DevOps config layers: environment secrets, defaults, and BYOK

Three layers keep secrets and product defaults from fighting each other. BYOK means Bring Your Own Key — customer-provided credentials for services such as AI providers, entered after the site is live.

The contract: Host env defines the install. Integrations define what that customer plugs in. The dacpac defines the schema both agree on.

Layer Owns Examples
Host / environment Install identity & secrets SQL connection, Stripe, mail, public base URL (Dockhand, Compose, Azure settings)
App defaults in the release Product behavior Non-secret defaults that ship with the image / base config
Admin → Integrations Customer-owned keys after go-live BYOK AI keys, QuickBooks Connect, and similar in-app connects

In-app BYOK is not a substitute for host Stripe on a dedicated commerce install; it is the workspace’s own credentials where the product supports them. When these three layers stay separate, the team can ship fast. When they bleed together, shipping means risking secrets or production data. The next post covers provisioning — how these layers come together when a customer checks out.

CI/CD release trains: the app assumes the schema is already there

Your CI/CD release train (or deployment pipeline) assumes the schema is already present before the site takes traffic. That is the key insight; everything below is how the handoff is enforced.

The contract: the running web app assumes the schema is already present. It does not invent tables on startup. DevOps owns making sure the dacpac landed before traffic does — image and schema are parallel artifacts, not a startup migration surprise.

Two artifacts move in parallel under CI/CD. The web application image carries Razor hubs, /api, and services. The dacpac carries schema. Pipelines should compile both so a broken database project fails before anyone points it at a real catalog. Applying the dacpac to a customer database is a deliberate publish step — by the provision pipeline on day zero, or by a controlled fleet/day-two republish with lockdown.

That split is why Part 3’s host can stay boring under load: the application process is not a migration engine.

CI/CD timeline (deployment pipeline)
  CI:     build web image + build dacpac
  Day 0:  provision applies dacpac to new SQL + starts site
  Day 2:  republish schema with SeedSandboxSampleData = 0 (lockdown)

Observability after deploy: enough signal, no secret sprawl

Once your dacpac and image are deployed, you need confidence the site is working — but not at the cost of leaking secrets into logs. You do not need a novel telemetry platform to operate dedicated installs; you do need enough signal to answer “which site” and “which request.”

Prefer request identifiers on hub and API failures, honest health endpoints for the site and for the provision worker, and rate limits on public or expensive surfaces so one noisy client cannot redefine your day. Logs should name the install and the failure mode — not dump secrets or full connection strings into a paste buffer.

When something fails during provision, the job status and the worker logs are the source of truth. When something fails after go-live, the site logs and the database are. Mixing those two timelines is how teams debug the wrong layer.

What this contract keeps in place

Local Docker is allowed to be messy about data. Shared catalogs and customer installs are not. The same dacpac family and the same application shape travel the path; seed posture and config identity change at each stop. Isolation from Part 5 only holds if DevOps refuses to “fix” a UI bug by deleting across workspaces or by republishing sample story data onto a live site.

That is the unlock: DevOps discipline lets the same product ship everywhere — laptop, lab, Azure — without casualizing production data. CI/CD keeps the image and the dacpac honest; config layers keep secrets in the right hands; lockdown keeps SandBox from returning as an accident.

The next post is where that contract meets the commercial trigger: paid checkout, the provision job, the out-of-process worker, and the six artifacts that turn a slug into a live template install. For a product-facing map of that story — template, modules, provision, and scale — see the Hivoltech / inveazy capabilities brief (PDF).


What’s next

Continue with Provisioning a customer site — paywall to job to listener to https://{slug}.inveazy.com, and the hard lessons that keep secrets out of the web app. Operator walkthroughs live in the separate how-to series.

Series:Multi-tenant design in practice · Next → Provisioning a customer site