# Progressive Deployment with Tags

> Release Builds gradually to your organization using deployment rings — map each ring to a Publication filtered by tags and promote a single Build from pilot to production.

Source: https://docs.applivery.com/en/app-distribution/distribute/progressive-deployment/  •  Last updated: 2026-07-30

**Key topics:** Deployment rings, Build tags, Publications, Access control, Applivery, Builds, User Groups, Audiences

---

**TL;DR:** Create one Publication per ring with Build selection set to Tags, assign each ring its own Groups or Audiences, then promote a release by adding the next ring's tag to the same Build.

Releasing a new version to everyone at once means that if something is wrong, everyone finds out at the same time. **Progressive deployment** — also called a staged or ring-based rollout — avoids that by releasing to a small group first and widening the audience only once the version has proven itself.

Applivery has no dedicated "rings" feature. You build them out of two things you already have: **tags on Builds** and **Publications that select Builds by tag**. The whole model fits in one sentence:

> A ring is a Publication that serves Builds carrying a specific tag. Promoting a release means adding the next ring's tag to the same Build.

## How rings map to Applivery

A typical setup uses three rings, though you can use as many as you need:

<table style="min-width: 75px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Ring</p></th><th colspan="1" rowspan="1"><p>Who it reaches</p></th><th colspan="1" rowspan="1"><p>Purpose</p></th></tr><tr><td colspan="1" rowspan="1"><p><strong>Pilot</strong></p></td><td colspan="1" rowspan="1"><p>A handful of people — QA, early adopters</p></td><td colspan="1" rowspan="1"><p>Catch obvious breakage before anyone else sees it</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Rollout</strong></p></td><td colspan="1" rowspan="1"><p>One or two departments</p></td><td colspan="1" rowspan="1"><p>Validate against real day-to-day usage</p></td></tr><tr><td colspan="1" rowspan="1"><p><strong>Production</strong></p></td><td colspan="1" rowspan="1"><p>Everyone</p></td><td colspan="1" rowspan="1"><p>General availability</p></td></tr></tbody></table>

Each ring needs two pieces:

-   **A tag** that identifies the ring, set on the Publication's Build selection.
    
-   **An access rule** — the User Groups or Audiences allowed to reach that Publication.
    

The Build itself carries no notion of a ring. It just carries tags, and the Publications decide what to do with them.

:::tip
When you upload a Build, tags are sent as a **comma-separated list**, so a comma inside a tag name splits it into two tags. Use hyphens instead: `ring-pilot`, `ring-rollout`, `ring-production`. Pick a convention and stick to it — the tag is what wires everything together.
:::

## Create a ring

A ring is an ordinary Publication with **Build selection** set to **Tags**. Create one per ring.

### From the Dashboard

**Open your App and go to Published Apps**

Create a new Publication.

**Set Build selection to Tags**

Enter the tag for this ring, for example `ring-pilot`. The Publication will serve any Build carrying that tag.

**Set Visibility and Security**

For internal rings, **Active** visibility with **Private** security is the usual combination — people sign in with their Applivery account or your Workspace SSO. Use **Unlisted** if you would rather share the ring by direct URL only.

**Restrict access**

In **Access control**, add the User Groups or Audiences allowed into this ring. See [Choosing who gets each ring](#choosing-who-gets-each-ring) below.

**Give it a recognizable slug**

For example `myapp-pilot`. The resulting URL is `yourworkspace.applivery.com/{slug}`.

You end up with one Publication and one URL per ring, each listening for its own tag.

For the full list of Publication settings, see [Distribute Apps](https://docs.applivery.com/en/app-distribution/distribute/distribute-apps/).

### From the API

The key parameter is `filter.type` set to `tag`, with `filter.value` holding the ring's tag. Full reference: [POST – Create a Publication](https://docs.applivery.com/en/app-distribution/api/publications/create-publication/).

**Integrations API** — scoped to a single App, authenticated with an App API Token:

```bash
curl 'https://api.applivery.io/v1/integrations/distributions' \
  -X POST \
  -H 'Authorization: Bearer YOUR_APP_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "slug": "myapp-pilot",
    "security": "logged",
    "visibility": "active",
    "filter": {
      "type": "tag",
      "value": "ring-pilot"
    },
    "groups": [["qa-team"]],
    "showHistory": true
  }'
```

**Workspace API** — Workspace-level, authenticated with a Service Account token:

```bash
curl 'https://api.applivery.io/v1/organizations/ORG_ID/stores/STORE_ID/pubApps' \
  -X POST \
  -H 'Authorization: Bearer YOUR_SERVICE_ACCOUNT_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "slug": "myapp-production",
    "security": "logged",
    "visibility": "active",
    "filter": {
      "type": "tag",
      "value": "ring-production"
    },
    "activateUserAudiences": true,
    "userAudienceMap": [
      { "id": "AUDIENCE_ID", "notifyNewBuildsProcessed": true }
    ]
  }'
```

`security` accepts `public`, `password` or `logged`. `visibility` accepts `active`, `inactive` or `unlisted`.

## Choosing who gets each ring

Access to a ring is controlled by the Publication's **Access control**, using either User Groups or Audiences.

-   [**User Groups**](https://docs.applivery.com/en/app-distribution/distribute/user-groups/) are hand-picked collections of people. Best for a pilot ring, where you want to name the exact testers.
    
-   [**Audiences**](https://docs.applivery.com/en/app-distribution/distribute/user-audiences/) are defined by rules and update on their own as people join or leave. Best for wider rings, where maintaining a manual list would be a chore.
    

A reasonable starting point:

<table style="min-width: 50px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Ring</p></th><th colspan="1" rowspan="1"><p>Typical access</p></th></tr><tr><td colspan="1" rowspan="1"><p><code>ring-pilot</code></p></td><td colspan="1" rowspan="1"><p>User Group — QA team, early adopters</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>ring-rollout</code></p></td><td colspan="1" rowspan="1"><p>Audience — a department such as IT or Support</p></td></tr><tr><td colspan="1" rowspan="1"><p><code>ring-production</code></p></td><td colspan="1" rowspan="1"><p>Audience — everyone in the Workspace</p></td></tr></tbody></table>

Via the API, `groups` supports AND/OR logic: each inner array is an AND clause and each outer element is an OR clause, so `[["group1","group2"],["group3"]]` means _group1 AND group2, OR group3_. It only applies when `security` is `logged`. For Audiences, set `activateUserAudiences` to `true` and list them in `userAudienceMap`.

## Promote a Build through the rings

Promotion does not involve rebuilding or re-uploading anything. It is the same Build gaining tags:

```text
Build #A (v2.0)
  1) tags: ring-pilot                              → visible in pilot only
  2) tags: ring-pilot, ring-rollout                → now also in rollout
  3) tags: ring-pilot, ring-rollout, ring-production → now also in production
```

### From the Dashboard

**Open the Build**

Go to **Builds** in your App and select the Build you want to promote.

**Add the next ring's tag**

Edit its **Tags** and add the tag for the next ring.

**Remove the tag from the previous Build**

If an older Build still carries that ring's tag, remove it so the ring serves exactly one Build.

The Build appears in that ring's Publication as soon as you save.

### From the API

Use [PUT – Update a Build](https://docs.applivery.com/en/app-distribution/api/builds/update-build/).

:::warning
The `tags` field **replaces the entire array**. Include every tag the Build should keep — if you send only the new tag, all the others are dropped.
:::

```bash
curl 'https://api.applivery.io/v1/integrations/builds/BUILD_ID' \
  -X PUT \
  -H 'Authorization: Bearer YOUR_APP_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "tags": ["ring-pilot", "ring-rollout"]
  }'
```

The Workspace API equivalent is `PUT https://api.applivery.io/v1/organizations/ORG_ID/apps/APP_ID/builds/BUILD_ID` with a Service Account token.

:::tip
Keep each ring's tag on **one Build at a time**. When you promote a new Build into a ring, remove the tag from the previous one. That way the version each ring is serving is never ambiguous.
:::

## One Build for all rings, or one Build per ring?

Prefer **one Build promoted across rings**.

<table style="min-width: 75px;"><colgroup><col style="min-width: 25px;"><col style="min-width: 25px;"><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p>Approach</p></th><th colspan="1" rowspan="1"><p>What it means</p></th><th colspan="1" rowspan="1"><p>Recommendation</p></th></tr><tr><td colspan="1" rowspan="1"><p>One Build → several rings</p></td><td colspan="1" rowspan="1"><p>Compile and upload once; the same binary moves forward by gaining tags</p></td><td colspan="1" rowspan="1"><p>Preferred</p></td></tr><tr><td colspan="1" rowspan="1"><p>One Build per ring</p></td><td colspan="1" rowspan="1"><p>Compile and upload a separate Build for each ring</p></td><td colspan="1" rowspan="1"><p>Only for specific cases</p></td></tr></tbody></table>

The single-Build approach wins for three reasons:

-   **You ship what you tested.** The exact binary your pilot group validated is the one production receives — no rebuild in between to introduce differences.
    
-   **Version metadata stays consistent.** Applivery reads version information from the package itself, so one Build means one set of version values across every ring.
    
-   **Traceability is simple.** One release equals one Build equals one history.
    

Uploading a distinct Build per ring only makes sense when the rings genuinely need different binaries — different build configurations, different endpoints baked in at compile time, and similar. Otherwise, promote by tag.

## Setting the initial tag at upload time

You can save yourself a step by tagging a Build into the first ring as you upload it. This is the natural fit for a CI pipeline, which uploads and drops the result straight into pilot:

```bash
curl 'https://upload.applivery.io/v1/integrations/builds' \
  -X POST \
  -H 'Authorization: Bearer YOUR_APP_TOKEN' \
  -F 'build=@myapp.aab' \
  -F 'versionName=v2.0' \
  -F 'tags=ring-pilot' \
  -F 'changelog=Sprint 42 release'
```

The response includes the Build `id`, which you will need to re-tag it later. See [POST – Upload a Build](https://docs.applivery.com/en/app-distribution/api/builds/upload-build/) for the full parameter list.

## Checklist

-   One Publication per ring, each with **Build selection = Tags**.
    
-   A tag naming convention agreed and written down, with no commas.
    
-   Access control set per ring — Groups for the pilot, Audiences for the wider rings.
    
-   CI uploads new Builds already tagged into the first ring.
    
-   Promotion means adding the next ring's tag to the same Build, and removing it from the previous one.
