Skip to content

Multiple Environments

small-config treats environments as a first-class concept. Every command accepts --env and each environment gets its own isolated state file.

How --env is resolved

When you run sc build, the CLI resolves the environment name in this order:

  1. --env <name> — explicit flag always wins
  2. CONFIG.default_env — if using config.py and --env is omitted
  3. "production" — fallback for legacy sc.py files without ProjectConfig
sc build                      # uses CONFIG.default_env (e.g. "dev")
sc build --env staging        # forces "staging"
sc build -e production        # short form

Per-environment state files

Each environment writes to its own state file:

Environment State file
dev sc.state.dev.json
staging sc.state.staging.json
production sc.state.production.json

State files are ignored by .gitignore (added by sc init). Each file tracks which components are deployed, their outputs, operator, and git SHA.

Legacy migration

If you have an existing sc.state.json from before per-env isolation was introduced, it is still used automatically when --env production is requested and sc.state.production.json doesn't exist yet. New writes will create the per-env file.

Branching in config.py

project() receives the env string. Use it to vary any aspect of the stack:

class AppConfig(ProjectConfig):
    default_env = "dev"

    def project(self, env: str) -> Project:
        if env == "production":
            project_id = "my-app-prod"
            machine_type = "n2-standard-4"
        else:
            project_id = "my-app-dev"
            machine_type = "e2-micro"

        infra = TerraformComponent(
            name="infra",
            vars={
                "project_id": project_id,
                "machine_type": machine_type,
                "env": env,
            },
        )
        return Project(name="my-app", env=env, components=[infra], tests=[...])

You can also branch on env to skip certain components entirely:

components = [infra]
if env == "production":
    components.append(monitoring)

Using environment names in Terraform

Pass env to Terraform as a variable so resource names (VMs, buckets, databases) are unique per environment:

# infra/main.tf
variable "env" {}
variable "project_id" {}

resource "google_compute_instance" "app" {
  name    = "app-${var.env}"
  project = var.project_id
  ...
}
vars={
    "project_id": project_id,
    "env": env,
}

This avoids collisions when dev and production live in the same GCP project.

Deploying and destroying per env

Each env is fully independent:

# dev (default)
sc build
sc destroy

# staging
sc build --env staging
sc destroy --env staging

# production
sc build --env production
sc destroy --env production

Locks are also per-env — a running sc build --env staging won't block sc build --env dev.

For small teams:

  1. default_env = "dev" — daily work uses dev by default, no --env needed
  2. sc build --env staging before every release to staging
  3. sc build --env production as the final deploy step (gate behind CI approval)