Projects and Environments
- A project is a named workload inside an organization. Its slug appears in the registry address every
terraform inituses, and there is no rename — you choose it once. - Every project has exactly three environments:
dev,staging, andprod. You never create or name them. - An environment is either protected or not. Protection is what turns a bind into a gated promotion.
prodis protected; the other two are not. - Archiving a project is a soft delete and it is one-way. It refuses new promotions, but it does not stop the registry serving what that project's environments are already bound to.
- Module aliases map the short module token in a registry URL to a real module identity, per project.
What a project is
An organization owns projects; a project organizes one workload. It carries a slug, a display name, an optional module-alias map, and an archived flag. That is the whole object — a project holds no compliance posture of its own. Posture is authored in configs and frozen into snapshots, and a binding is what connects a snapshot to one of the project's environments.
The reason a project exists as a separate object is addressing. When Terraform asks the registry for a module, the request names an organization, a project, an environment, and a module:
The organization alias is covered on organizations and members. The remaining three slots are this page's subject.
Naming a project
The slug is a URL component, so its grammar is narrow:
| Rule | Detail |
|---|---|
| Character set | Lowercase letters, digits, and hyphens |
| First and last character | Must be a letter or a digit — no leading or trailing hyphen |
| Length | Between 2 and 40 characters; a single-character slug is rejected |
| Not allowed | Uppercase letters, underscores, dots, and any other punctuation |
Two properties follow that are worth knowing before you pick a naming scheme.
The registry matches the slug exactly. It is not lowercased, and separators are not normalized. web-platform and web_platform are different strings, and only the first is a legal slug. A URL carrying a slug that does not exist verbatim reports that there is no binding for that project and environment — it does not tell you the project name was wrong. If a terraform init reports no binding on a project you are sure is bound, check the slug character by character before you go looking at the binding.
This is deliberately unlike the module token in the last slot, which is matched separator-insensitively. Do not carry the module slot's tolerance over to the project slot.
Slugs and display names are both fixed at creation. There is no route that renames either. A slug is unique within its organization, and archiving a project does not release it — the record stays, so creating a new project with an archived project's slug is refused as a conflict. If you need a different name, create a new project and promote to its environments.
The three environments
Every project has dev, staging, and prod. That set is fixed: no environment is created, named, renamed, or removed, and asking a project for its environments always returns those three.
A stored record for one appears the first time you act on it — binding a snapshot, or proposing a waiver. Before that the environment is reported from defaults, which is why a brand-new project shows three environments with nothing bound rather than an empty list.
The environment slot in a registry URL must be exactly dev, staging, or prod. A typo does not produce an "unknown environment" error. The request simply stops being recognized as a project address and is read as a plain module path instead, which then fails as an unknown module. If a URL you expected to work reports a missing module rather than a missing binding, suspect the environment segment.
Protection
An environment is either protected or not:
- Unprotected. Binding a snapshot is a direct action. No gate runs.
- Protected. Every bind runs the promotion gate first, and the gate can refuse. Binding also requires a higher permission than publishing does.
prod is protected. dev and staging are not. What the gate actually checks, and what a refusal looks like, is on promotion and gates.
Be aware that protection is not self-service today. There is no route or setting that changes an environment's protection, so the arrangement above is the arrangement you get. If your control framework requires a gated staging, raise it with us rather than looking for a toggle.
Archiving a project
There is no delete. The only way to retire a project is to archive it, which sets a flag and leaves the record in place.
What archiving does:
- Removes the project from the project list by default. The list can be asked to include archived projects, and reading a single archived project still works so the interface can show it as read-only.
- Refuses further promotion. Binding, promoting, previewing a promotion, listing the environments, reading environment status, and diffing a config against it are all reported as not found.
- Records an audit event, like creating a project does.
What archiving does not do — and each of these has bitten someone:
- It does not stop the registry serving. The registry never consults the archived flag. A
terraform initagainst an archived project's environment continues to resolve and continues to serve exactly what that environment is bound to, including a binding whose frozen waivers have since lapsed. - It cannot be undone. No action sets the flag back. Archiving is a one-way decision.
- It does not free the slug, as above.
- It does not remove the binding. There is no unbind: an environment's snapshot pointer can be replaced but never cleared.
So archive a project only once you are content for its environments to keep serving whatever they are bound to. If your intent is to change what an environment serves, promote first and archive afterwards.
Module aliases
The last slot in a registry URL is a short module token, not a full module address. By default that token resolves against the published terraform-aws-modules collection: s3-bucket reaches terraform-aws-modules/s3-bucket/aws, so most projects need no configuration at all.
A project's module aliases override and extend that. An alias maps one token to an explicit module identity — a namespace, a name, and a provider — which lets a project point a familiar token at a different module, most often an internal fork. Aliases are checked before the built-in names, so an alias named after a terraform-aws-modules module shadows it for that project and only for that project.
Two things about aliases that shape how you use them:
- Token matching ignores case and separators on the alias-key side. An alias stored as
s3_bucketis reachable ass3bucketors3-bucket. Two keys that differ only in separators are therefore the same alias, and storing both is refused. - Aliases are saved as a whole map, not one at a time. Saving replaces the stored map entirely, so any key you leave out is removed.
Operating projects
The rest of this page is the operator's half: the exact alias validation contract, which the registry's own parsing must match byte for byte; where an alias falls through to and what happens when it resolves to nothing; what to do when the alias limit is reached; why archive exists instead of delete, and what is still reachable on an archived project; and the permission each action requires.
Related
- Organizations and members — the organization alias that fronts every registry URL
- Configs and snapshots — where compliance posture is authored, since a project holds none
- Promotion and gates — bindings, the promotion gate, waivers, rollback, audit
- Access control — the full role and permission model
- Registry tokens — how a
terraform initauthenticates to a project's environment