Knowledge base · KB Article

Branches, tags, and environments: how Coho ships content

90Ø120 pitchØ60 pitchAB
Fig. 01 Spur wheel and pinionThirty teeth to fifteen: the wheel turns once for every two turns of its pinion.
Kind
KB Article
Published
19 Oct 2026
Reading
15 min
Centres on
coho branch create feature/articles

Applies to: every Coho project

You will learn: what branches, tags, and environments are, why Coho keeps them apart, and how to release, promote, and roll back content


The short version

Coho gives three separate jobs to three separate things:

You author on branches, release by cutting a tag, and publish by pointing an environment at that tag. Each step is its own deliberate action, and each can be done by a different person.


Why Coho works this way

Most content management systems give you some version of this: a draft and published flag, a staging copy of your space, a sandbox you clone and then reconcile by hand, or a fixed set of environments the vendor picked for you. Whatever the shape, you have to squeeze your team's real workflow into it, and the same few things go wrong:

  • "Published" means two things at once. It is both what the content is and where it is live. Changing one drags the other along.
  • Environments drift. When staging is a copy, it slowly stops matching production, and nobody can say exactly how.
  • "What is live right now?" has no precise answer. You get the latest state of a database, not a named, reviewable release.
  • Rolling back is a restore, not an undo. Going back means re-publishing old content, re-running a migration, or restoring a backup, usually while the site is broken.

Coho takes the approach software teams settled on long ago: you author to versions, and you point environments at them to publish. Keeping those two concerns apart is what makes the rest of this article possible:

  • Deployments and rollbacks are almost instant. Promoting a release moves a pointer. Nothing is copied, merged, or rebuilt at that moment.
  • What your site serves is read-only until you promote. Editors can work all day on a branch, and production does not change until someone explicitly promotes a new release.
  • Delivery is fast. Every release is turned into a read-optimized snapshot in advance, so your site reads from something that was finished before it went live.

Branches: where you work

A branch is a line of work. Your content and your content model (types, fields, and validation) live on branches together, so a schema change and the content that goes with it travel as one change.

There are two kinds, and a branch's name tells you which:

  • Version branches start with v, for example v0.0.x or v1.2.x. A version branch is a long-lived release line. Every new project starts with one, v0.0.x, as its trunk. Only version branches can be released.
  • Feature branches have a prefix, for example feature/pricing-redesign or hotfix/broken-cta. You branch them from a version branch (or from a tag), do the work, review the diff, and merge them back.

Creating a branch copies nothing and takes the same time whatever the size of your project, so branch as often as you like.

coho branch create feature/pricing          # from the current ref, and switches to it
coho diff v0.0.x                            # what this branch would change, conflicts included
coho merge --into v0.0.x -m "New pricing"   # lands whole, or not at all

Tags: what you released

A tag is a release: a frozen point on a version branch. Once a tag exists, it never changes and it cannot be deleted. v1.4.0 means the same content and the same content model today, next month, and next year.

coho tag create v1.4.0 --branch v0.0.x -m "Q3 catalogue"

When you cut a tag, Coho starts building a snapshot of it in the background: a read-optimized copy of every entry at that point, prepared for delivery. You do not manage snapshots yourself. They are why promotion and rollback are fast (see below).

Because a tag is permanent, you can always ask "what changed between these two releases?" with a diff, and get an exact answer:

coho diff v1.3.0 v1.4.0

Environments: where a release is running

An environment is a named pointer. prod does not hold content. It points at a tag, and your site reads whatever that tag contains.

A new project starts with these four (on the Free plan, with dev and prod only):

dev is the one deliberate exception to "environments point at tags". It follows a branch so your team can preview work as it happens. Environments like this are live environments. The rest are snapshot environments, and they only ever serve a finished release.

Several environments can point at the same tag. After a release reaches production, stage and prod often both point at v1.4.0, and they share one snapshot.


The release flow

  feature/pricing ──●──●──╮
                          │ merge
  v0.0.x  ──●──●──●───────●──●──▶      branch: authors keep working
                  │          │
                tag        tag
               v1.3.0     v1.4.0       tags: frozen releases
                  │          │
  prod ───────────┘          │         prod  → v1.3.0
  qa ────────────────────────┘         qa    → v1.4.0
  1. Author on a feature branch, review the diff, and merge it into the version branch.
  2. Release by cutting a tag from the version branch.
  3. Promote the tag through your environments, one decision at a time:
coho promote qa v1.4.0       # waits for the snapshot to be ready
coho promote stage v1.4.0
coho promote prod v1.4.0 -m "Q3 catalogue launch"

You can do the same from the Environments page of your project in the Coho portal.


Deployments and rollbacks are almost instant

Promoting is moving a pointer. When you promote prod to v1.4.0, Coho copies no content, runs no merge, and rebuilds nothing. It changes where prod points and records who did it. Your site sees the new release within seconds.

Coho will not promote a release that is not ready. If the tag's snapshot is still building, the promotion is refused with SNAPSHOT_NOT_READY (the CLI simply waits for it). So the moment prod moves, the release behind it is already built and ready to serve. There is no cold start at the moment your traffic arrives.

Rolling back is the same move in the other direction:

coho rollback prod

This points prod back at the tag it was on before. Coho keeps recent releases ready to serve, so going back to the release you just left is normally as fast as the promotion was. Nothing is restored and nothing is re-published. (Going back further can take a little longer; see Does a rollback rebuild anything?)

A rollback is conditional. It only goes ahead if prod is still on the release you are rolling back from. If a teammate has already fixed forward, your rollback is refused with ENVIRONMENT_MOVED rather than undoing their fix.

Every move is recorded. An environment's history shows where it pointed, who moved it, when, and why:

coho env history prod

Promotions and rollbacks appear as one plain sequence, so "what was in production on 3 March, and who put it there?" is a lookup, not an investigation.


Read-only until you promote

A snapshot environment serves exactly one tag, and a tag cannot change. This gives you a guarantee rather than a convention:

  • Editing never changes what production serves. Authors write to branches. A save, a merge, or even a schema change on v0.0.x reaches prod only after someone cuts a tag and promotes it.
  • Content cannot be edited "in production". There is nowhere to write it. To change what is live, you branch (from the live tag, if it is urgent), make the change, and release it.
  • The delivery API has no write operations. It serves published releases and nothing else.
  • Delivery is separate from authoring. The service that serves your snapshot environments reads only prebuilt releases. It does not connect to the authoring database, so your site keeps serving even if authoring is unavailable.

The only things that change what a snapshot environment serves are an explicit promotion, a rollback (which is a promotion), or unsetting the environment. Each one is a permissioned action that is recorded in the history.

Live environments such as dev are the intended exception: they follow a branch so you can preview unreleased work. Because that content has not been released, live environments are private. They need a signed-in person or a project token, and they are never served from the public edge.


Fast by design

  • Releases are built before they are served. Each tag becomes a read-optimized snapshot. Reading from a snapshot environment is a lookup into finished data, not a query assembled at request time.
  • Environments share snapshots. Ten environments pointing at the same tag share one snapshot, so adding an environment costs one pointer, not a copy of your content.
  • Caching follows the model. Content read from a tag by name can be cached indefinitely, because it can never change. Content read through an environment is cached briefly, so promotions and rollbacks are seen within seconds.

Who can do what

Releasing and promoting are separate permissions on purpose. Producing a release and deciding where it runs are different decisions, often made by different people on different days.

The default project roles:

The permission is the gate, not the order. You may promote a tag straight to prod without passing through qa, if you hold the permission for prod. Coho allows it so that an emergency fix does not have to queue behind a full test cycle, and it warns you (SKIPPED_TIER) and records the skip in the history.


Shaping your own pipeline

dev, qa, stage, and prod are a starting point, not a requirement. Your pipeline is a list of tiers, and each tier holds one or more environments:

  • A regulated team might add a uat tier between stage and prod for sign-off, with its own permission.
  • A global team might put prod-us and prod-eu in the prod tier, both pointing at the same release.
  • An agency might give each client review its own environment in the qa tier.
  • A team that wants personal previews might add a live preview tier, so authors can preview their own feature branches without being able to move the shared dev.
coho tier create uat --resolves snapshot --ord 3        # goes before prod, which moves to 4
coho env create uat --tier uat                          # an environment in it
coho env create prod-eu --tier prod --target v1.4.0     # a second production environment
coho tier move uat --ord 2                              # changed your mind: before stage

A tier's position is just that: where it sits in the pipeline, counted from 0. Creating or moving a tier to a position another tier holds moves that tier, and the ones after it, along by one. The order never blocks a promotion; it decides which tiers count as skipped.

Creating, moving, and removing tiers is for project owners, because a tier decides who may promote to it. Adding an environment needs the permission to promote to its tier.

Free and Site projects keep the pipeline they start with: owners can reorder its tiers, but adding tiers or environments needs the Enterprise plan.

Permissions attach to the tier, not the environment. Anyone who can promote to the prod tier can promote to every environment in it, and adding prod-eu needs no role changes. If two environments need different permissions, put them in different tiers.

Each tier is either snapshot (its environments point at tags) or live (its environments follow branches). Live environments on feature branches count toward a limit set by your plan, and Coho refuses one over the limit with LIVE_ENVIRONMENT_LIMIT.


Working on two releases at once

Teams often run two lines of work side by side: a short-term release, say 1.4.0, going out in a few weeks, and a long-term one, 2.0.0, a redesign that will take months. In Coho each line is a version branch with its own releases:

coho branch create v2.0.x --version --from v1.4.x    # release managers

Authors work on v1.4.x (or feature branches off it) for 1.4, and on v2.0.x for 2.0. Releases are tagged on their own line: v1.4.0 and v1.4.1 from v1.4.x, v2.0.0-beta.1 from v2.0.x. The question is where each line gets previewed and tested.

Option 1: one dev and one qa, taking turns

With the four environments a project starts with, both lines share dev and qa, so you swap them between the lines:

coho promote dev v2.0.x            # dev now shows the 2.0 work
coho promote qa v2.0.0-beta.1      # qa tests the 2.0 beta
coho promote dev v1.4.x            # back to 1.4 for a fix
coho promote qa v1.4.1

Each swap is instant and recorded in the environment's history, so this works when the second line needs testing only now and then. The costs show up as the lines get busier:

  • Only one line can be previewed or tested at a time. The 2.0 testers wait while qa is on 1.4, and the other way round.
  • Everyone has to know which line dev and qa are on right now before trusting what they see.
  • Warnings get noisier. Coho warns when a release reaches stage without passing through qa, and it checks where qa points now. Once qa has moved on to the 2.0 beta, promoting the 1.4 release it already tested warns that qa was skipped.

Option 2: one environment per line, in the same tiers

Give each line its own environments, and each environment its own site:

stage and prod stay as they are, because only one line ships at a time.

coho env create dev1 --tier dev --target v1.4.x
coho env create dev2 --tier dev --target v2.0.x
coho env create qa1 --tier qa --target v1.4.1
coho env create qa2 --tier qa --target v2.0.0-beta.1

You can name them however you like. Keeping the starting dev and qa for 1.4 and adding dev2 and qa2 for 2.0 works just as well.

Each site reads its own environment by name: qa2.mysite.com asks for qa2, and sees exactly what qa2 points at. The qa sites read with a delivery key, like production. The dev sites follow branches, so they are live environments, which are private: they read with a project token rather than a delivery key (see Read-only until you promote).

Why put them in the existing tiers rather than new ones:

  • No permission changes. dev1 and dev2 are both in the dev tier, so whoever can promote to dev can move either one, and the same goes for qa1 and qa2. Adding them needs no new roles, and a maintainer can create them, because creating an environment needs only the permission to promote to its tier.
  • Warnings understand it. A release counts as having been through qa if any environment in the qa tier points at it, so v1.4.1 sitting on qa1 is enough when it moves to stage.
  • Nothing is copied. Environments are pointers. When qa1 and stage point at the same release, they share one snapshot.

What it needs from your plan. This setup has more environments than the four a project starts with, so it needs the Enterprise plan. Each live environment that follows a branch other than your project's first version branch also counts toward your plan's limit on live environments, and Coho refuses one over the limit with LIVE_ENVIRONMENT_LIMIT.

Keeping the lines in step

Whichever option you choose, a fix made on v1.4.x has to reach v2.0.x too, or 2.0 ships without it. Merge the short-term line into the long-term one after each fix:

coho merge v1.4.x --into v2.0.x

Coho reminds you when you forget. The branch list shows how many changes v1.4.x holds that v2.0.x does not, and cutting a tag on v2.0.x warns (PENDING_FORWARD_PORT) while any are outstanding. The worked example below shows the same step for an urgent fix.


Worked example: an urgent fix

prod is on v1.2.0, from the v1.2.x line, and your team has already moved on to v1.3.x. A broken link has to be fixed in production today.

coho branch create hotfix/cta --from v1.2.0    # branch from exactly what is live
# fix the link, then review it
coho diff v1.2.x
coho merge --into v1.2.x -m "Fix the broken call to action"
coho tag create v1.2.1 --branch v1.2.x -m "Hotfix: call to action"
coho promote prod v1.2.1 -m "Hotfix"           # allowed; SKIPPED_TIER is recorded
coho merge v1.2.x --into v1.3.x                # carry the fix forward

The last step is the one teams forget, and forgetting it means the bug comes back in the next release. Coho watches for it: the branch list shows how many changes a version branch holds that its successor does not, and cutting a tag on v1.3.x warns you (PENDING_FORWARD_PORT) if the fix has not been carried forward yet.


Common questions

Can I delete a tag? Not today. A tag is the permanent record of a release: environment history refers to it, and it is what you roll back to. You can delete an environment, and the removal is recorded in its history.

Can I fix a typo directly in production? Not directly, by design. Branch from the tag prod points at, fix it, merge, tag, and promote. For a one-line change that takes a few commands, and production never holds content that was not released.

How quickly does my site see a promotion or a rollback? Within seconds of Coho accepting it. Coho only moves an environment to a release that is ready to serve, so only the pointer has to reach the delivery service.

Does a rollback rebuild anything? Usually not. Every release keeps a stored copy of its content for as long as its tag exists. On top of that, Coho keeps a ready-to-serve copy of every release an environment points at, and of the ten most recent releases in your project. Rolling back to one of those, which normally includes the release you just left, is instant.

An older release may have had its ready-to-serve copy removed to save space. Rolling back to it first rebuilds that copy from the stored one, without touching your content: coho rollback waits for it, and the portal shows the release as Rebuilding until it can be promoted. Your site keeps serving the current release in the meantime.

What does dev show? The latest saved content on v0.0.x, live. Point it at a different branch with coho promote dev <branch>.

Can I preview a release before it reaches production? Yes. Promote it to qa or stage, or read the tag directly by name with a delivery key. A tag that no public environment points at still requires a key, so release candidates are never public by accident.