Delivery and Operations
Environments: Keeping Verification Away from Real Resources
How to describe an environment as a contract of code, configuration, data, access and external dependencies, so that checking a change never quietly…
Worktrees and Isolated Environments separated a Git worktree from a container and a sandbox. Database Migrations separated a database schema written in code from the actual state of the data. Continuous Integration and Continuous Delivery and Deployment covered checking a change and releasing it in a controlled way. This unit ties those threads together: how to isolate work, keep schema apart from data, and confirm a change before it ships. An environment is more than a folder, a server or the word staging in a dashboard. It is the combination of code, runtime, configuration, data, accounts, network and external dependencies that determines what the system will actually do.
In this unit, a local environment means running on a developer's machine; a shared development environment is a separate system where the team integrates unfinished work; a preview is a short-lived version for one specific pull request (PR); staging is a system for checking a change before release; and production is the system customers use. Teams may name these levels differently, merge some of them or not have all of them. What matters is the environment's contract, not the number of levels. Addresses are written as URLs (Uniform Resource Locators — the address of a resource on the network), and software integrations are called APIs (Application Programming Interfaces) — the rules by which programs send each other requests and responses.
After this unit you can:
- describe an environment in terms of code, runtime, configuration, data, access and external dependencies;
- decide which properties must match for a trustworthy check and which must deliberately differ from production;
- explain the difference between settings read at build time and settings read while the program runs;
- check whether a preview or staging environment is talking to the right database, account and external service;
- plan a preview for each PR, including its cost, the evidence it keeps and its clean-up;
- tell the environment an agent works in apart from the product environment the agent might touch.
An environment is a contract
An environment's contract answers plain questions: which version of the code is running, who starts it, which resources it can see, who is allowed to change them, where its data comes from and when the environment stops existing. If the answers live only in one person's head or in an outdated .env file, the system has hidden configuration.
A practical description covers at least the following:
| Part | What to record |
|---|---|
| Code and startup | Code revision, build method, runtime version and compatible platform settings. |
| Configuration | Service addresses, feature flags, the environment's name and the variables that change behaviour. |
| Secrets | Owner, storage location, assigned roles and how a secret is replaced. The values themselves must never go into a log. |
| Data | Schema, test data sets, the source they are copied from, sensitivity, retention period and deletion method. |
| Access | Cloud provider account, database user, permitted operations, network restrictions and the people who can approve an action. |
| Dependencies | Email and payment providers, file storage, queues, webhooks and other systems outside the application. |
| Lifecycle | Who creates the environment, what signal shuts it down, what gets deleted, what evidence of its work remains and who pays for the resources. |
A managed service takes some operational work off your hands — maintaining database servers, for example — but it does not choose the target project, roles, data retention, budget or deletion rules for you. The environment's owner remains responsible for those decisions.
A staging label does not, by itself, create a separate server, database, key or access boundary. In GitHub Actions, an automation script (a workflow) can bind a particular job to an environment with approval rules, branch restrictions and its own secrets. Only when a required reviewer is configured for the environment does the job wait for that approval, and the environment's secrets stay unavailable until it is given. These rules have to be set up, and approvals and some other features depend on the repository type and the GitHub plan. If a job uses its own machine to run on (a self-hosted runner), the named environment does not isolate that machine's file system or access; GitHub advises treating its secrets as being as exposed as repository secrets. GitHub describes environments and plan limits and the caveats for self-hosted runners, accessed 24 September 2026.
Not every product needs the same levels
A convenient starting model looks at five common levels, but treat it as a menu of decisions, not a standard pipeline everyone must build.
| Level | What it is for | Typical data and risks |
|---|---|---|
| Local run | Changing code quickly and investigating bugs. | A local database, synthetic records or faked dependency responses. Risk: local configuration may point at a shared or production resource. |
| Shared development | Bringing several developers' changes together in a common system before stable verification. | Shared test accounts and data. Risk: one person's changes can get in another's way; this does not replace a separate preview for a specific PR. |
| PR preview | Giving reviewers an address for one specific change before merging. | Separate test data and keys; you need to check who can see the address and which side effects are possible. |
| Staging | Checking stable integrations, the sign-in domain, migrations and scenarios a local mock cannot reproduce. | Often a shared environment. Parallel changes, stale data and manual edits can create drift. |
| Production | Serving real users and real operations. | Real data and external effects; access and changes need a clear owner and an evidence log. |
A small static site may need nothing more than a local run, a preview and production. A service with sign-in, payments, background imports and migrations may need staging or an isolated database for every significant change. "More environments" does not mean "more reliable": every level has to be maintained, updated, secured and cleaned up.
Comparable conditions matter more than a copy of production
Environment parity means that two systems share exactly those properties the team wants to draw conclusions about. Full identity is often impossible and undesirable: a test system does not need production's scale, and it must not hold production's personal records. On the other hand, the same schema version, the same authorization rules and a similar shape of test data can be critical.
| For a convincing check, it is usually worth matching | It is fine, and often necessary, to make deliberately different |
|---|---|
| Code revision, runtime version, compatible platform flags and the sequence of migrations. | Number of processors, disk size and traffic volume, unless the check is about scaling. |
| The schema contract, access rules, order of calls and the version of a critical integration. | Account identifiers, service addresses, secrets and keys. These must point at separate test resources. |
| The structure of the data, edge cases and the business-process states you need. | The data values. For a preview, synthetic records prepared in advance for repeatable tests (fixtures) are usually enough, rather than a copy of the live database. |
| The behaviour you are checking in email, payments, webhooks and background jobs. | Recipients and external side effects: emails and payments must go to test recipients or to the provider's sandbox. |
Code can be bit-for-bit identical and still behave differently because of a different database address, flag, key, schema or user role. So "staging is running the same commit (the same recorded snapshot of the code)" does not answer the question "is this payment safe to run in staging?"
If the team has obtained a copy of production, the word "anonymised" does not prove that a person cannot be re-identified. Before copying you need a lawful basis, defined fields, a reproducible transformation, a check of derived identifiers and deletion deadlines. It is often cheaper to prepare a handful of synthetic records that cover the states you need than to move the whole database.
Build-time and runtime settings
A build-time setting is read when an artifact is prepared from the code. A runtime setting is read by a process that is already running. These are different moments with different consequences. For example, if a web page baked the API address into its generated browser files during the build, changing the variable in a cloud dashboard will not, on its own, rewrite the files that have already been built.
On Vercel, environment variables can be read during the build or while a function runs; changes to them apply to the next deployment, not to one that already exists. A preview value can be assigned to all non-production branches or to a single branch; a branch-specific setting overrides the general one. This is a rule of one particular platform, not a universal property of hosting. Vercel explains environment variables and when they are used, accessed 24 September 2026.
A local .env.local file is a convenience for the developer, not a secret store. Do not add it to Git, do not paste a production value into a prompt to an agent, and do not print a key into a log. A Git worktree does not automatically reproduce every local or ignored file; in Codex-managed worktrees some files can be copied explicitly through .worktreeinclude, but secrets should not be spread that way. The Codex documentation on worktrees, accessed 24 September 2026, describes the limits of this behaviour.
A value in the code has to be safe even if someone reads it. A database password or secret token belongs in a secret store and should be supplied only to the process that needs it. A public URL, an environment=preview label or a random domain is not a secret and does not isolate data.
Data, accounts and side effects
Check not only that the application "can see the database", but exactly which project, branch, account and set of permissions it is connected to. A variable named DATABASE_URL_PREVIEW tells you nothing about the value inside it. A safer process compares a harmless project identifier against an expected list and stops if a preview has been given a production identifier. There is no need to print the password or the full connection string.
Give the database a separate user with the minimum permissions required; give email a test mailbox, payments a test account and webhooks a test receiving address; and give queues and schedules a separate namespace with an explicit switch-off rule. Otherwise an innocent-looking run can send a real message, repeat a payment, change a shared cache or start a background job against production records.
An environment contains more than a web server and a database: it can include worker processes, file volumes, certificates, network rules, job schedules and external identities. HTTP (Hypertext Transfer Protocol) governs the exchange between a client and a web server, and DNS (the Domain Name System) maps a domain to a network address; a correct page does not prove that a background process is reaching the correct resource. A server managed by a provider still needs an owner for its configuration, access, updates and backups.
Worked example: a shop's preview received the production database address
First, let us separate a real report from a teaching scenario. In GitHub issue Appsmith #25544, the author reported that in a Git-connected application, a query created for the production data source kept running after the environment was switched to staging. The title and steps mention staging, while in the Environment field the author entered Deploy Preview. The author closed the issue, but the public page contains no explanation of a fix, no linked code change and no confirmation of any write to production data. So the issue illustrates a dangerous class of configuration mismatch, but gives no grounds for claiming what caused the behaviour or that data was changed.
Now the teaching scenario. An online shop has opened a PR with a new import of a supplier's catalogue. A reviewer opens the preview and sees real products, although they expected a synthetic set. The team does not run the import and does not attempt a test write. It compares the database's harmless project identifier with the expected preview identifier, checks the name of the selected environment and where the variable's setting comes from. Nobody opens the password or the full connection string, and neither is added to a log. In this teaching scenario, the check shows that the same connection string used for Production had been explicitly copied into the Preview settings. That is the cause in the invented example only; it is not an explanation of the Appsmith issue.
The possible consequences depend on the permissions of that account. A read-only role can still display real prices, stock levels or personal details in the interface. A write role can change the catalogue or trigger follow-up messages to the supplier. Naming the branch preview restricts none of this.
The team repairs the contract in several places. It sets up a separate preview database or branch, creates a role for it with minimal permissions and test data, and keeps the production secret and the route to production out of the preview process altogether. The Preview configuration holds only the separate test connection. Before the address is opened, the application compares the safe identifier of the project it actually selected with the expected one; a mismatch stops the deployment. After the change, the team sees only synthetic products and can check the import without any access to production records.
If the provider does not support a separate database per PR, the team can use a single controlled staging environment with its own account and a data set that can be reset. This isolates parallel PRs less well, but it is still safer than a shared production credential. The identifier check is a useful emergency latch, but it does not replace distinct credentials and the absence of any network route to production.
Infrastructure as code and drift
Infrastructure as Code (IaC) is a way of describing the cloud resources you need in controlled configuration, so the team can see what is supposed to exist. The actual state often differs from what is written down: someone changed a value in a dashboard, used a different account, deleted a resource or connected a different region. This difference is called configuration drift.
With Terraform, the team compares the declaration, the state file and the real resources. terraform plan -refresh-only shows how Terraform would update its own record of the infrastructure; the plan itself changes neither the state file nor the resources. A confirmed terraform apply -refresh-only changes the state file, but it does not restore or rebuild real resources. An ordinary apply can change infrastructure, so before running it you need to understand the desired state and check the account, region and proposed actions. In HashiCorp's tutorial, a wrong region makes Terraform mistakenly believe that an existing resource is missing; the authors explicitly advise against applying such a refresh plan. HashiCorp's official tutorial on refresh-only mode, accessed 24 September 2026.
For example, a wrong provider account or region can make Terraform think that a known database has disappeared. Before confirming any plan, check the organisation, account, project, region, state-file storage and the list of changes. Terraform and Kubernetes are not mandatory for every application: the same principle of keeping desired and actual state under control can start with a verifiable dashboard setting and a short change log.
Preview environments must be restricted and cleaned up
A preview is useful when a reviewer needs to open the site built from the current branch. But that address may be reachable by others, may have real integrations, or may leave a database and secrets behind after the PR is finished. A random identifier in a URL is not access control. Vercel, for example, lets you require authentication or another form of protection, and a policy determines which addresses it applies to. Vercel's documentation on Deployment Protection, accessed 24 September 2026.
Tie a preview to clear lifecycle events:
- Create it only for changes that need it, such as user interface (UI) work or migrations.
- Pass in test data and separate secrets; switch off real payments, emails, scheduled jobs and webhooks.
- Link the URL, branch, commit, deployment and database branch so they can be cross-checked.
- When the PR closes, delete temporary databases, test credentials, domains and other created resources; keep only the logs and results needed for audit.
- Check that another environment sharing the same database or domain is not deleted along with the preview.
Providers have different rules. A Supabase preview branch can be deleted automatically after the PR is merged or closed; a persistent branch used for staging is not deleted automatically. Branches consume compute, disk space and bandwidth; this usage counts towards the plan quota but is not covered by Supabase's Spend Cap, so the bill can grow even with the cap set. Supabase describes the branch lifecycle and branch costs and usage accounting, accessed 24 September 2026.
Preview sites have their own retention policy. On Vercel, how long deployments are kept depends on the plan and the team's policy; an active branch or an alias (a domain link pointing at a deployment) can extend the life of a particular version. So closing the PR and deleting the database do not necessarily remove the site's address and files. Vercel describes its deployment retention rules, accessed 24 September 2026. Keep links to the commit, check statuses, environment identifiers and clean-up times, but not secret values or personal data from logs.
Worked example: a catalogue preview with its own database
Imagine a PR to an internal equipment catalogue: it adds a "storage location" field and changes a filter. The site is deployed on Vercel, the data lives in Supabase, and a comment on the PR shows a separate preview address. This is an example of one specific supported connection between two platforms, not a recipe for any hosting.
When the PR is opened, the Supabase integration links the site's branch to a matching preview branch of the database and updates the Vercel variables. This happens when the PR is opened, not merely when the branch is created. The site's build may start before the new database addresses reach its settings. Supabase describes this race and automatically redeploys the latest preview after synchronisation. So the author checks the commit identifier and the database branch in the final deployment. Documentation of the Supabase integration with Vercel, accessed 24 September 2026.
In GitHub-integration mode, the database branch takes its schema from the migrations stored in the repository. No ready-made schema is copied: it is created by applying the migration files. Production rows and files in object storage are not carried over; test rows can be loaded with a seed.sql script that inserts predefined records. That file is written in Structured Query Language (SQL), the language used to talk to a relational database. If a required migration is missing, the preview will have a different structure and the test will not check the change. This is not the same as a branch created through the Supabase dashboard: you need to know which mode is in effect. Documentation of the Supabase GitHub integration, accessed 24 September 2026.
Before review, the team checks the site's commit, the database identifier and branch, the migration result and the test records. The branch settings contain only the preview address; the server's production secret is never passed into browser files or into automated check jobs. The address is protected separately and checked from the reviewer's account: the mere fact that the page opens does not prove it is private. Only then does the team look through the test catalogue and confirm that the changed schema and filter work.
After the PR is merged or closed, the temporary Supabase branch is deleted according to its policy, but the Vercel deployment has its own retention period. Changing a variable does not rewrite a page that has already been built — that needs a new deployment. The owner checks the state of both systems, revokes temporary access that is no longer needed and keeps harmless evidence of the check.
A preview like this is useful when several PRs change the schema and test data in parallel. For a small team that changes the schema once a month, a separate database per PR may cost more than one stable staging environment with test records.
Platform note: the first Vercel deployment
Vercel's documentation warns that the first deployment of a new project is always treated as Production, even if it was started from another branch or without the --prod flag. In the open GitHub issue Vercel #17069, created on 13 July 2026, the author reported that a new project received target=production even though they had explicitly marked the first attempt as a preview. The author cancelled and deleted the deployment before any domains were assigned; as of 10 October 2026 the issue is open and does not explain the cause separately from the current documented rule. Vercel also offers a separate staged production deployment, a production build you can check before domains are assigned, but it uses production variables and may reach production services and data. Such an address is not a safe substitute for staging. At the date of checking, Custom Environments, including staging, are available on the Pro and Enterprise plans; check the current plans before choosing. Vercel's documentation on the first deployment, staged production and the staging environment, accessed 10 October 2026. To deploy from a terminal, Vercel uses a command-line interface (CLI); do not rely only on the branch name or a CLI flag.
Worked example: a local sign-in mock hides a staging failure
An invented teaching example. In a subscription-management service sold as software as a service (SaaS), a developer is checking a new roles screen. Locally, the code talks to a fake sign-in provider (a mock), a test stand-in for the real service, which always returns a synthetic editor with the member role. The screen opens, the test API responds and the local scenario passes.
In staging, the real test provider refuses to send the user back to the application. The address the user returns to after sign-in (the callback URL) is out of date and missing from the list of permitted addresses (the allowlist). Once that is fixed, another difference may turn up: staging talks to a different customer organisation (tenant), to a server other than the one that issued the signed token (the issuer), or returns a different set of roles. The local success proved that the screen works with the mock, but it proved nothing about the redirect, the token signature or the permissions of the test organisation.
The team compares the safe configuration parameters: the test organisation's name, the application identifier, the token issuer, the permitted callback address, the domain and the available roles. The token itself and the client secret are not copied into a log or into a prompt to the agent. Staging holds its own secret, tied only to the test organisation. After the fix, a user from the test organisation signs in, sees only a test subscription and cannot reach the production organisation.
The mock remains useful for quick checks of screen states and error responses. It does not replace an end-to-end staging scenario with the real test provider. If no such test account exists, the team should state clearly what the local check does and does not prove, and must not replace it with signing in as a real customer.
The agent's environment is not the product's environment
The agent's environment answers the question "where does the agent read and change files, and which commands and network destinations are available to it?" The product environment answers the question "which resources serve users, and what data do they hold?" Separately, you have to establish which credentials give the agent access to which resource.
| Boundary | What it controls | What it does not prove on its own |
|---|---|---|
| Working copy or Git worktree | A separate folder of files and a code branch, isolated from other uncommitted changes. | Whether the database address in the configuration points at a local database, staging or production. |
| Agent sandbox | Depending on its settings, it can narrow file reads/writes and outbound network access. | That the mounted files contain no production secret, or that a permitted network resource is safe. |
| Product environment | The site version, configuration, data, workers, integrations and user roles. | Where exactly the agent's commands ran and what other resources it can control. |
Codex can work locally, in a Git worktree on the same computer or in a remote cloud environment. In Codex, the setup commands that prepare such an environment have internet access, while the agent's own network access is switched off by default and can be separately restricted or allowed. Claude Desktop separately offers local, remote and SSH (Secure Shell, a secure protocol for controlling a remote computer) sessions. Claude Code has tool permission rules and a sandbox that restricts the file system and network for Bash and PowerShell commands. Codex describes its environment modes and cloud environments, accessed 24 September 2026; Claude describes its sandbox, permissions and Desktop remote environments, accessed 24 September 2026.
If the agent works in a separate worktree but can see production credentials or has network access to the live database, Git isolation does not protect the data. If the agent works in a remote sandbox but the workflow has handed it a production key, the sandbox does not turn that key into a test one. Protection comes from several things together: the minimum necessary credentials, a separate test identity, an explicit list of permitted resources, network rules, an action log and approval for operations on real data.
An instruction in AGENTS.md or CLAUDE.md, or the phrase "work only with staging", explains intent, but does not by itself restrict the network or the file system. Check separately which permissions the agent's runtime enforces and which policy applies at the cloud provider. A machine that runs automated check jobs and has access to a staging secret does not become safe just because the workflow is called preview.
Worked example: choosing an environment setup for a small studio
An invented teaching example. Three people are building a client portal for a booking studio. Interface changes appear several times a week, but the payment callback, the booking schema and administrator permissions affect real customers. Every level has a price: infrastructure, maintenance time, expectations about test data and configuration mistakes.
The team compares these options:
| Option | What the team gets | Cost and risk |
|---|---|---|
| Local run + web preview + production | Small text and interface changes can be reviewed at a separate address without a separate test database. | Does not check real sign-in, integrations or migrations; the preview may connect to the same services as production. |
| Local run + one persistent staging + production | A stable test domain, separate keys and database, and the ability to run a real test callback and migration. | Staging has to be regularly reconciled with the code and schema; people can get in each other's way if test data is shared. |
| Local run + a separate preview for each PR + staging + production | Parallel checking of isolated migrations and UI states. | More databases and secrets to create and delete, higher cost, more ways to drift and harder to consolidate results. |
The team chooses a local database with synthetic test records, one persistent staging environment for integrations, and production. For a PR that changes only text or styles (Cascading Style Sheets, CSS), a web preview is enough; for a schema migration or several parallel data changes, a separate database branch is added. The owner writes down how to check the database project and user, which external calls are switched off and who checks that resources have been cleaned up.
This is the author's adaptation of ideas from two books to the choice of preview environments: an architectural decision should be judged by its trade-offs, and a platform should be built to reduce the team's unnecessary cognitive load. This setup will change if previews become slow because of shared data, if several teams appear, or if regulations require isolated copies. According to Software Architecture: The Hard Parts, architectural decisions carry the weight of trade-offs rather than having one universally best pattern. From Team Topologies, second edition, the team takes the idea of cognitive load: a minimal platform should provide ready test data and checks as an understandable service, so developers do not have to recreate infrastructure by hand for every change. It should remove recurring confusion, not create a mandatory manual queue for every test deployment.
Summary
An environment is code, configuration, data, access, external dependencies and an owned lifecycle. A preview or staging environment is convincing only when the team knows which resources it is connected to, compares the properties that matter for the check, and keeps production data and credentials apart. Drift can be detected, but it should not be "fixed" automatically without understanding the desired state. Create a temporary environment where it reduces a specific risk, and protect and clean it up according to the provider's rules. The agent's working sandbox and the deployed product system remain separate boundaries, and access between them has to be checked separately.
Sources and further reading
Materials were checked on 24 September 2026. Online documentation without a numbered version is cited as current at the access date; plan limits and integration behaviour should be checked again before setting anything up.
- GitHub, Deployments and environments — wait-timer or approval rules, branch restrictions and access to secrets for a configured environment; accessed 24 September 2026.
- GitHub, Secure use reference — security limits of self-hosted workflow runners; accessed 24 September 2026.
- Vercel, Environments, current page, accessed 24 September 2026 — Local, Preview, Production, staging options, the first deployment and checking a production build before a domain is assigned.
- Vercel, Environment variables, updated 17 September 2026, accessed 24 September 2026 — build-time and runtime variables, branch-specific settings and changes applying to a new deployment.
- Vercel, Deployment Protection, updated 7 January 2026, accessed 24 September 2026 — ways to protect addresses and the limits of what they cover.
- Vercel, Deployment Retention, updated 16 September 2026, accessed 24 September 2026 — deployment retention periods and the effect of plan, branch and domain alias.
- Supabase, Branching, accessed 24 September 2026 — temporary and persistent branches and their lifecycle.
- Supabase, GitHub integration, accessed 24 September 2026 — migrations, seed records, and the fact that production data and files are not carried over to a branch.
- Supabase, Integrations, accessed 24 September 2026 — matching the site branch to the database branch, when synchronisation happens and the redeployment after a possible race.
- Supabase, Manage Branching usage, accessed 24 September 2026 — compute, disk space, data transfer, plan quota and the lack of Spend Cap coverage.
- HashiCorp, Use refresh-only mode to sync Terraform state, current web tutorial, accessed 24 September 2026 — reviewing drift and the difference between a refresh-only plan, a state-file change and real infrastructure.
- OpenAI, Codex environments, Git worktrees and Cloud environments, accessed 24 September 2026 — where Codex runs, which files can be carried into a managed worktree, and how network access is split between setup commands and the agent.
- Anthropic, Claude Code Desktop, permissions and sandboxing, accessed 24 September 2026 — local, remote and SSH sessions; tool permissions and file-system and network limits.
- Matthew Skelton and Manuel Pais, Team Topologies, second edition (2025), authors' book page and key concepts, accessed 24 September 2026 — cognitive load and the platform as the thinnest useful internal service. Based on the authors' publicly available materials, not the full text of the book.
- Neal Ford, Mark Richards, Pramod Sadalage and Zhamak Dehghani, Software Architecture: The Hard Parts (October 2021), author's page and O'Reilly publisher's page, accessed 24 September 2026 — decisions as trade-offs between alternatives. Based on the authors' and publisher's available descriptions, not the full text of the book.
- Appsmith, GitHub issue #25544, created 20 July 2023, status Closed as of 10 October 2026 — a report of a staging query that ran against a production data source; the public page does not reveal the root cause or any impact on data.
- Vercel, GitHub issue #17069, created 13 July 2026, status Open as of 10 October 2026 — a user report of
target=productionon a first deployment from the terminal, compared against the current rule in Vercel's documentation.