Skip to content

Blog

Copy the environment shape, not the production secrets

How Appaloft turns environment copy into a plan-first workflow: topology and intent travel; databases, domains, volume data, and secret values stay behind decisions.

AppaloftEnvironmentsDeployment controlCLISafety

Copying production into staging sounds like a one-line operation.

In practice, that one line is where a lot of quiet damage happens. A staging app starts talking to the production database. A preview reuses a production custom domain. A developer environment inherits a live secret reference because the platform treated “same shape” as “same values.”

Appaloft already has a narrow clone path: environments.clone copies environment-owned variables and creates a new environment namespace. That is useful, and it is deliberately incomplete. A real staging or developer environment also needs resource topology, runtime profiles, dependency bindings, route intent, and storage requirements. The hard part is copying that shape without dragging production-only values along for the ride.

This post is about that boundary: Environment Profile Duplication. It sits next to What a deploy URL should promise, How a custom domain gets connected to Appaloft, and Deployment failures should leave a recovery path. Those posts talk about routes, domains, and recovery after a deploy. This one is about what should happen before the new environment is even allowed to deploy.

Shape Is Not State

An Environment Profile is not a new owner of resources. It is a provider-neutral composition snapshot used for planning, diffing, and syncing:

  • resource topology and kinds;
  • source and runtime profiles;
  • network intent;
  • variable requirements;
  • dependency binding intent;
  • route intent;
  • storage requirements;
  • selected policy intents such as auto-deploy or preview base selection.

It is not a dump of production state. It does not own databases, custom domains, secret material, or deployment history.

That distinction matters because Appaloft keeps ownership boundaries explicit. Resource remains the deployable unit. Dependency Resource owns managed or imported Postgres, Redis, object storage, and external services. Domain Binding owns hostname governance. Deployment remains one immutable execution attempt. Environment Profile Duplication is a workflow over those owners, not a shortcut that bypasses them.

If the product only had one command called copy everything, those boundaries would collapse under convenience.

Plan First, Then Decide

The safe default is plan-first.

appaloft env copy production staging --dry-run

That maps to environments.plan-duplicate. The plan should say what can be copied as shape, what must be regenerated, what needs an explicit decision, and what remains deferred. It should never return secret values.

Then apply with reviewed decisions:

appaloft env copy production staging \
  --dependencies create-new \
  --secrets regenerate \
  --data empty \
  --domains generated \
  --storage empty

The apply path is environments.duplicate-profile. Compatibility aliases such as appaloft env duplicate plan and appaloft env duplicate apply exist, but the product language we want people to remember is simpler: copy the profile, review the plan, then apply decisions.

Two related operations complete the day-to-day loop:

  • appaloft env diff-profile compares shape and decisions between environments without exposing secret values;
  • appaloft env sync-profile imports selected missing shape into an existing target instead of overwriting target-only choices.

Copy creates a new environment from a base profile. Diff and sync keep two environments from drifting in the wrong places after that.

The Decisions That Matter

Most copy bugs hide in four places: dependencies, secrets, domains, and storage data.

Dependencies

A source environment that uses managed Postgres should not silently hand that same database to staging. The plan can offer:

Decision Meaning
create-new-managed Provision an isolated target dependency and bind a generated secret reference
bind-existing Attach the target resource to a dependency that already exists in the target context
reuse-source Keep the source dependency only after explicit acknowledgement
defer Create shape now, block deployment until the binding is resolved

reuse-source is allowed because some teams intentionally share a read-only analytics database or a temporary debugging connection. It is never the silent default. When it is chosen, the product should keep a shared-source warning visible in the read model and audit trail.

Secrets

Secret values do not travel. At best, the target gets a new secret reference or a deferred decision. A copied environment that still prints production connection strings in logs has already failed the model, even if the deploy “works.”

Domains

Production custom domains do not copy by default. The plan regenerates a platform route or asks for an explicit target hostname. That is the same ownership idea as custom-domain onboarding: a hostname is a claim about who controls it, not just a string field on an environment.

Storage data

Volume requirements can copy. Volume data cannot copy by implication. Empty volume, restore from backup, or import from artifact has to be an explicit decision. “Make staging look like production” is a data migration problem. It should not hide inside environment creation.

Deferred Is Safer Than Guessing

If a required dependency or secret decision is deferred, deployment admission should fail closed.

That sounds strict. It is better than the alternative: a half-copied staging environment that deploys against production because the platform filled in the blank.

Deferred decisions also make AI agents and CI safer. An agent can create the target environment, report which decisions remain open, and stop before it claims success. A GitHub workflow can fail on unresolved profile decisions instead of treating “environment created” as “ready for traffic.”

The control-plane rule is the same one we use for install progress and deploy URLs: intermediate success is not final success. Shape without resolved bindings is not a deployable environment.

What Still Does Not Copy

Even after apply, several things stay out of the silent path:

  • dependency secret material;
  • production database, cache, or storage data;
  • custom domains and generated routes that need target-specific ownership;
  • resource-level variables and access profiles that still need target review;
  • storage attachments that require explicit data decisions;
  • deployment history.

Some of those become follow-up work on the target environment. That is intentional. Environment copy prepares a reviewable shape. It does not pretend to finish every operational decision in one button.

There is also a product honesty constraint on the Console side. Public Appaloft exposes neutral extension points for staged workflows. The hosted Cloud Console can present a multi-step copy experience. First-party Community Web still treats full staged Duplicate / Diff / Sync UI as a follow-up. The CLI and API are the stable contract today; the Console experience can be richer where a distribution implements it.

Why Not Just Clone Variables

Leaving environments.clone around is deliberate for now.

Variable-only clone is still the right answer when someone needs a new empty namespace with a few environment-owned settings. Profile duplication is the right answer when the application shape itself needs to move.

Merging them too early would either make clone dangerously broad or make profile copy harder to explain. So the docs keep the split:

  • clone: variables and environment identity;
  • profile copy: topology and intent, with decisions for environment-specific values.

If that split becomes confusing later, we can deprecate or alias. We should not paper over it by making one command mean both things.

A Safer Copy Leaves Fewer Surprises

After a successful profile copy, the useful result is not “staging exists.”

The useful result is:

  • the target environment has the intended resource shape;
  • dependency bindings are either isolated, explicitly shared, or clearly deferred;
  • no production secret value was returned or stored as part of the plan;
  • production domains were not silently rebound;
  • deployment is blocked until required decisions are resolved;
  • diff and sync can keep the shape honest over time.

That is more ceremony than a naive clone. It is also closer to how people already think when they are careful: copy the app layout, decide what the new environment attaches to, then deploy.

If you try the CLI path, start with --dry-run and look at the decisions before you apply anything. If a decision feels too automatic, that is usually a sign the platform should ask again, not that the operator should trust the default harder.