Skip to content

Environments

Add an environment name to any command to target it: blogwright deploy staging. Without one, every command uses production.

An environment is a name that selects the config file, is stamped into every AWS resource name, and keys the deployment state. Two environments in the same account never share a bucket, a role or a state file, so production and staging run side by side without touching each other.

Pass it as the positional [env] argument or with --env:

Terminal window
blogwright deploy # production
blogwright deploy staging # positional
blogwright deploy --env staging # flag - wins over the positional
blogwright status staging # every environment-scoped command takes [env]

rollback, logs and verify take a <hash> first, then the optional environment. With --hash, the only positional is the environment:

Terminal window
blogwright rollback 4f3a2b1c staging
blogwright logs 4f3a2b1c staging
blogwright verify 4f3a2b1c staging
blogwright verify --hash 4f3a2b1c staging

Names must be lowercase letters, digits and dashes (^[a-z0-9-]+$). Anything else is rejected before anything in your account is created or changed.

  1. Create config/staging.jsonc with the same siteName as production. The environment prefix keeps the resources apart:

    config/staging.jsonc
    {
    "region": "us-east-1",
    "siteName": "example", // same slug as production
    }
  2. Create its infrastructure: blogwright bootstrap staging

  3. Deploy to it: blogwright deploy staging

To give staging its own hostname, add a domain - see Custom domains. To let CI deploy it, set githubRepo - see CI deploys with GitHub OIDC.

Want one file for every environment instead? See Configuration files.

One AWS profile and account per environment

Section titled “One AWS profile and account per environment”

Each environment file can name the profile its commands use and pin the account they must run against, so staging and production can live in different AWS accounts without anyone exporting AWS_PROFILE by hand:

config/staging.jsonc
{
"region": "us-east-1",
"siteName": "example",
"aws": { "profile": "example-staging", "accountId": "210987654321" },
}

With a pin in place, credentials for the wrong account fail before any state is read - so status can never report an empty stack, and bootstrap can never build a second one, in an account you did not mean. Run blogwright whoami staging to check which account and profile a command would use. See aws for the precedence rules.

Each environment gets one S3 bucket, <env>-<siteName>-<accountId>, holding the build artifacts, the live site and the state. Every other resource name carries the same environment and site name - the build and exec roles, the builder MicroVM image, the log groups - so each environment is a fully isolated stack. The layout is in the architecture reference.

The state lives at state/<env>.json in the environment’s own bucket. Because it is in S3 rather than on disk, your laptop, a colleague’s and CI all see the same record of what has been provisioned.

Most behaviour is identical. Search-engine settings are the exception: with seo.robots and seo.sitemap at their auto defaults, production is indexable and gets a sitemap, and every other environment blocks crawlers and skips the sitemap. A staging site never leaks into search results by default. See robots.txt and sitemap.xml.

Every AWS resource blogwright creates carries two tags:

  • environment - the environment name.
  • app - the app config option, falling back to the site’s domain (--domain or domain), then to the repo directory name. The tag is always something a person can trace back to the project in a billing or resource listing.

Site files get the same two tags as S3 object tags on upload. Preview deploys stamp the PR id into the object tag (environment: preview-pr-42), so per-PR cost and cleanup queries work - see PR previews.