Skip to content

Branch Deployments

Deploy multiple branches of the same application side by side. Each branch gets its own database, drives, cache namespace, containers, ports, routing, and domain — completely isolated from other branches.

Promotion is not available

Branch isolation is fully implemented and described below. What does not exist yet is promotion — there is no way to point the canonical URL at a non-main branch, and no mecca deploy promote / demote command. Each branch is reachable only at its own branch URL.

Why Branch Deployments?

Without branches, deploying from feature/payments overwrites the same containers and database as main. With branch deployments, you can:

  • Test feature branches against a real environment before merging
  • Run staging and production from different branches of the same repo
  • Compare behaviour between branches with isolated databases
  • Promote a tested branch to production without rebuilding

Quick Start

# Deploy main (the default)
mecca app deploy myapp -e dev

# Deploy a feature branch alongside main
mecca build trigger -e dev -g git@github.com:yourorg/myapp.git -b feature/payments

# Both are now running with separate URLs
mecca app list -e dev

Output:

Branch             Status     URL
main               running    myapp-web-yourgroup.dev.appmecca.net
feature/payments   running    myapp-feature-payments-web-yourgroup.dev.appmecca.net

The Branch Flag

Most CLI commands that operate on a deployment accept -b / --branch:

mecca app deploy myapp -b feature/payments -e dev
mecca app undeploy myapp -b feature/payments -e dev
mecca app show myapp -b feature/payments -e dev
mecca logs myapp -s backend -b feature/payments -e dev
mecca var set myapp KEY=value -b feature/payments -e dev
mecca app backup myapp -b feature/payments -e dev

The default when -b is omitted depends on the command:

Command Default when -b is omitted
mecca app deploy, mecca build trigger, mecca register main
mecca app undeploy The branch that is actually running
mecca app show, mecca app restart, mecca app backup The app's production branch
mecca var set / list / unset Every branch
mecca logs Every branch (logs are not branch-scoped)

There is no APPMECCA_BRANCH variable

Unlike APPMECCA_ENV, no environment variable sets a default branch. You must pass -b explicitly on every command that needs a non-default branch.

Branch URLs

Each branch deployment gets a unique URL. The full pattern is:

https://{app}-{branch}-{service}-{group}.{env}.{domain}

The main branch is the exception — it carries no branch segment:

Branch Service URL
main web https://myapp-web-yourgroup.dev.appmecca.net
main backend https://myapp-backend-yourgroup.dev.appmecca.net
feature/payments web https://myapp-feature-payments-web-yourgroup.dev.appmecca.net
feature/payments backend https://myapp-feature-payments-backend-yourgroup.dev.appmecca.net

The service name is always present

Even a single-service app has its service name in the hostname — there is no bare myapp.dev.appmecca.net URL. Run mecca app show myapp -b <branch> to see the exact URLs AppMecca assigned.

Branch name sanitisation

Branch names are sanitised for use in URLs: / becomes -, uppercase becomes lowercase, consecutive hyphens collapse, and leading/trailing hyphens are stripped. For example, feature/My-Branch becomes feature-my-branch.

What Gets Isolated

Each branch deployment is fully independent. Here's what's isolated:

Resource Isolation
Containers Separate container per branch (hash-based names)
Docker network Per-deployment bridge network — containers from different branches cannot communicate
Database Separate PostgreSQL database per branch
Cache Shared Redis, but isolated via the CACHE_PREFIX environment variable
Drives Separate drive contents per branch — data never crosses branches
Buckets Separate bucket per branch
Routing groups Separate routing group per branch
Router rules Separate routing rules per branch
Ports Independent host-port allocation per branch (7001-8999)
Environment variables Separate env var set per branch
Backups Separate backup paths per branch
DNS Separate DNS records per branch

What's Shared

Some resources are shared across all branches:

Resource Why
Image repository Images are tagged by commit hash — reusable across branches
Build project Branch is a build parameter, not a project
Git SSH key One key per environment, works for all branches
Database server Shared managed PostgreSQL server, but a separate database per branch
Cache server Shared managed Redis, isolated by key prefix

Building for a Branch

Specify the branch when triggering a build:

# Build a feature branch (a deploy is queued automatically on success)
mecca build trigger -e dev -g git@github.com:yourorg/myapp.git -b feature/payments

# Build only — no deploy
mecca build trigger -e dev -g git@github.com:yourorg/myapp.git -b feature/payments --no-deploy

The build checks out the specified branch, reads its appmecca.yaml, and builds the Docker images. The images are tagged with the commit hash from that branch.

Same commit, same image

If two branches are at the same Git commit, they produce the same Docker image. Only the deployment context (database, env vars, routing) differs between branches.

Deploying a Branch

# Deploy the latest build for a branch
mecca app deploy myapp -b feature/payments -e dev

# Check deployment status (pass the action ID the deploy printed)
mecca queue status <action-id> -e dev

# Check branch details
mecca app show myapp -b feature/payments -e dev
Expected output
App:         myapp
Environment: dev
Branch:      feature/payments
Status:      running
Image:       appmecca/dev/myapp/web:abc1234
Services:
  web: running (healthy)
    https://myapp-feature-payments-web-yourgroup.dev.appmecca.net

First Deploy for a Branch

When you deploy a branch for the first time, AppMecca automatically creates:

  1. A branch-specific routing group
  2. A router rule for the branch URL
  3. A separate database (if your app uses one)
  4. Containers on a dedicated Docker network
  5. Host-port allocations for this branch

All subsequent deploys of the same branch reuse this infrastructure.

Listing Branches

mecca app show reports one branch at a time (the production branch when -b is omitted). To see every branch that is deployed, use mecca app list — it prints one row per app/branch:

mecca app list -e dev
Expected output
App     Branch             Status     Health   URL
myapp   main               running    healthy  myapp-web-yourgroup.dev.appmecca.net
myapp   feature/payments   running    healthy  myapp-feature-payments-web-yourgroup.dev...
myapp   hotfix/login       running    healthy  myapp-hotfix-login-web-yourgroup.dev...

There is no dedicated mecca app branches command.

Production Promotion

Planned — not yet available

Promoting a branch to the canonical production URL (and the mecca deploy promote / demote commands) is part of the blue-green deployments feature, which is on the roadmap but not yet implemented. There is currently no mecca deploy promote or mecca deploy demote command.

Until blue-green ships, each branch is reachable only at its own branch URL (myapp-<branch>-<service>-<group>.{env}.{domain}), and the unbranched myapp-<service>-<group>.{env}.{domain} URL serves the main branch. To make a different branch the one users hit, merge it into main and deploy, or front it with a custom domain (see Custom Domains).

Managing Branch Deployments

Environment Variables

Environment variables are stored per branch — but mecca var set writes to every branch by default. Narrow it with -b:

# ⚠ No -b: writes DATABASE_POOL to EVERY branch of the app
mecca var set myapp DATABASE_POOL=10 -e dev

# Set a variable for the main branch only
mecca var set myapp DATABASE_POOL=10 -b main -e dev

# Set a different value for the feature branch
mecca var set myapp DATABASE_POOL=5 -b feature/payments -e dev

# -b also accepts a comma-separated list and glob patterns
mecca var set myapp DATABASE_POOL=5 -b 'feature/*' -e dev

# List variables for a specific branch
mecca var list myapp -b feature/payments -e dev

The KEY=value pairs are positional and come after the app name. Multiple pairs can be set in one command.

Changes need a redeploy

Setting a variable does not restart anything. The new value reaches your containers on the next deploy or redeploy.

AppMecca automatically injects two branch-related system variables into every container:

Variable Example value Description
APPMECCA_BRANCH feature-payments The branch this container is deployed from (sanitised)
CACHE_PREFIX myapp:feature-payments: Prefix to use for cache keys (cache isolation)

This is a container variable, not a CLI one

APPMECCA_BRANCH is injected into your running containers. Setting it in your own shell does not change which branch the mecca CLI targets — only the -b flag does that.

Database Operations

Each branch has its own database. Databases are provisioned automatically — there is no create command and no mecca db command group. Set database: enabled: true in appmecca.yaml and deploy; the platform creates the database, its user and credentials on the first deploy and injects DATABASE_URL into every container. Re-deploying is idempotent.

Read DATABASE_URL, never guess the name

The database and user names are opaque hashes (for example am_3f9a2c1b_db) — they are not {app}_db. Always connect using the injected DATABASE_URL.

# Back up a branch's database + drives as one bundle
mecca app backup myapp -b feature/payments -d "pre-merge" -e dev

# List bundles for a branch
mecca app backups myapp -b main -e dev

# Cross-branch restore: restore main's data into the feature branch
mecca app restore myapp <bundle-prefix-from-main> -b feature/payments --force -e dev

Cross-branch restore

You can restore a bundle from any branch into any other branch. This is useful for cloning production data into a feature branch for testing.

Starting a Branch From Another Branch's Data

To copy another branch's current data — no bundle to find — use restore-from-branch. It takes a fresh backup of the source now, backs up the destination first so you can undo it, and then restores:

# Copy main's database into feature/payments (the default component)
mecca app restore-from-branch myapp --from main -b feature/payments --confirm myapp -e dev

# Database, drives and buckets; wait and print what was copied
mecca app restore-from-branch myapp --from main -b feature/payments \
  --components db,drives,buckets --confirm myapp --wait -e dev
  • --components picks db, drives and/or buckets; the default is db alone.
  • Buckets are merged: an object the destination already has is kept unless you pass --overwrite-objects, and nothing is ever deleted. The copy is refused if the source buckets are over 250,000 objects or 50 GiB.
  • The destination must have been deployed at least once. For a new branch, start it from another branch when you register it instead — its first deploy is seeded:
mecca register git@github.com:you/myapp.git -b feature/payments \
  --from-branch main --components vars,db,drives -e dev

In the dashboard, use Restore from branch on the app's Database tab, or the Start from section when you Deploy another branch. Your profile's New branches card sets whether that section starts on Nothing or on Production.

Production is never overwritten

Data can be copied from the production branch, never into it — at the request and again when the agent runs the copy, in case production moved in between. If registering a branch would make it production, the copy is refused and the message names the mecca app set-production command to run first.

The copy is a snapshot

The source keeps running during the backup, so the copy reflects the moment it was taken; writes that land mid-backup may or may not be included.

To bring an existing database onto the platform, upload a pg_dump with the dashboard's Database tab, picking the feature/payments branch.

Service Management

Service commands are branch-scoped. Note that the service name is a positional argument on app restart, but the -s/--service flag on mecca logs:

# Show the services running for a branch
mecca app show myapp -b feature/payments -e dev

# View logs for a branch's service
mecca logs myapp -s backend -b feature/payments -e dev

# Restart one service on a specific branch
mecca app restart myapp backend -b feature/payments --force -e dev

# Restart every service on the branch
mecca app restart myapp -b feature/payments --force -e dev

Rollback

A rollback operates within a branch — re-deploy an older image of that branch with --no-build, which leaves other branches untouched:

# Roll the feature branch back to a known-good image
mecca build list -a myapp -e dev            # pick a SUCCEEDED build's Tag
mecca app deploy myapp --image-tag <old-tag> --no-build -b feature/payments -e dev

See Rolling Back for the full procedure.

Undeploying a Branch

Undeploy removes a specific branch's deployment:

# Undeploy a feature branch
mecca app undeploy myapp -b feature/payments --force -e dev

This removes the branch's containers, routing group, router rules, and DNS records. The database, drives and backups are preserved — use mecca app delete myapp -b feature/payments for full cleanup. There is no delete-branch command.

Other branches are unaffected

Undeploying one branch never touches another. Each branch has its own containers, routing and database.

Deleting a Branch

To fully clean up a branch deployment (including database, backups, secrets, and registry records):

mecca app delete myapp -e dev -b feature/payments

This removes:

  • Containers and Docker network
  • Routing group and router rules
  • Database and database credentials
  • Environment variables
  • The branch's drives and buckets
  • App-registry rows in the platform database

Images are preserved

Docker images are shared across all branches and are not deleted when a branch is removed. They can be reused if the branch is redeployed later.

app delete drops the branch's database

The database is backed up and then dropped. Bundle backups you took with mecca app backup survive the delete — take one first if the data matters.

Network Isolation

Each branch deployment runs on its own Docker network. Containers within the same branch can communicate freely, but containers from different branches (or different apps) are isolated:

┌──────────────────────────────────┐    ┌──────────────────────────────────┐
│ Docker Network: myapp:main       │    │ Docker Network: myapp:feature/x  │
│                                  │    │                                  │
│  frontend ──► backend            │ ✗  │  frontend ──► backend            │
│                                  │    │                                  │
└──────────────────────────────────┘    └──────────────────────────────────┘
                                 Cannot communicate

This isolation is enforced by Docker's network layer. No configuration required — it's automatic.

Cache Isolation

Branches share the same Redis instance but are isolated via key prefixes. AppMecca injects a CACHE_PREFIX environment variable into every container:

main:            CACHE_PREFIX=myapp:main:
feature/payments: CACHE_PREFIX=myapp:feature-payments:

Your application should use this prefix when constructing cache keys:

import os

CACHE_KEY_PREFIX = os.environ.get("CACHE_PREFIX", "")

cache = Cache(app, config={
    "CACHE_TYPE": "redis",
    "CACHE_REDIS_URL": os.environ["REDIS_URL"],
    "CACHE_KEY_PREFIX": CACHE_KEY_PREFIX,
})
import os

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": os.environ["REDIS_URL"],
        "KEY_PREFIX": os.environ.get("CACHE_PREFIX", ""),
    }
}
const prefix = process.env.CACHE_PREFIX || '';

async function cacheGet(key) {
  return redis.get(`${prefix}${key}`);
}

async function cacheSet(key, value, ttl) {
  return redis.set(`${prefix}${key}`, value, 'EX', ttl);
}

CI/CD Integration

GitHub Actions

name: Deploy Feature Branch
on:
  push:
    branches-ignore:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build and deploy branch
        env:
          APPMECCA_API_URL: ${{ secrets.APPMECCA_API_URL }}
          APPMECCA_API_KEY: ${{ secrets.APPMECCA_API_KEY }}
        run: |
          mecca build trigger \
            -e dev \
            -g ${{ github.server_url }}/${{ github.repository }}.git \
            -b ${{ github.ref_name }}

GitLab CI

deploy-branch:
  stage: deploy
  variables:
    APPMECCA_API_URL: $APPMECCA_API_URL
    APPMECCA_API_KEY: $APPMECCA_API_KEY
  script:
    - mecca build trigger
        -e dev
        -g $CI_REPOSITORY_URL
        -b $CI_COMMIT_BRANCH
  except:
    - main

Deploy-on-success is the default

mecca build trigger queues the deploy automatically — there is no --deploy flag. It also returns immediately, so the pipeline step passes before the build finishes; poll mecca build status <id> if you need the step to reflect the real outcome.

Without the CLI — a deploy hook

Both examples above install the CLI and hand it an API key, which is a credential scoped to everything you can do. If the pipeline only needs to say "deploy this app", a deploy hook is a better fit: one URL, one secret, scoped to exactly one app on one branch.

Create it with the CLI:

mecca hook create <app> --action build_and_deploy --name github-actions

or from the app's Webhooks tab in the dashboard. For a script, add --json and read the two values straight out:

mecca hook create <app> --action build_and_deploy --json > hook.json
jq -r .secret      hook.json    # the secret — shown once
jq -r .trigger_url hook.json    # the full URL to POST to

The secret is shown once, on create and on rotate — it is stored hashed, so no later command can recover it. Store it as a CI secret straight away, and store trigger_url as a CI variable too. It is the hook's public address, shaped like https://api.<env>.appmecca.net/api/hooks/<id> — use the value the API returns rather than building it yourself. (url in the same response is only the path; if trigger_url ever comes back empty, the API could not confirm its own public address, so join url onto the API address you already use, such as the one in mecca configure list.) Then the whole pipeline step is:

      - name: Deploy
        run: >-
          curl -fsS -X POST
          -H "X-Hook-Secret: ${{ secrets.APPMECCA_HOOK_SECRET }}"
          "${{ vars.DEPLOY_HOOK_URL }}"

action picks what firing does: redeploy (re-deploy the last built image, no rebuild), rebuild (build only), or build_and_deploy.

If the secret leaks, revoke it — the next call is refused immediately, and nothing else you own is affected:

mecca hook list <app>            # find its id
mecca hook revoke <app> <id>

To keep the hook but change its secret, mecca hook rotate <app> <id> mints a new one and prints it once. To pause a hook without revoking it (its secret is unchanged), use mecca hook disable / mecca hook enable.

No pipeline at all — deploy on git push

If the pipeline exists only to call AppMecca, you can skip it. Create a hook with kind: "git" and your provider, paste the returned URL and secret into GitHub's or GitLab's webhook settings, then turn deploy-on-push on for the branch. From then on, every push to that branch builds and redeploys it.

mecca hook create <app> --kind git --provider github --name push-to-deploy

It prints the URL and the secret, and for GitHub the exact settings to use. With --json, the URL to paste is the response's trigger_url.

In GitHub: Settings → Webhooks → Add webhook, content type application/json, paste the secret, and choose "Just the push event". In GitLab: Settings → Webhooks, paste the secret as the Secret token, and tick Push events.

In the dashboard, the dialog picks the provider for you

Choosing Git push in the New webhook dialog pre-selects the provider your repository is on, and an Automatic setup panel says whether your own linked account could let AppMecca add the webhook for you — and if not, why. Picking the other provider is flagged, because a webhook there would never receive a push.

Let AppMecca add the webhook for you

Instead of pasting the URL and secret yourself, AppMecca can create the webhook on GitHub or GitLab using your own linked account. In the New webhook dialog, tick Create it on GitLab for me (or GitHub) — it appears only when your account can do it — and confirm the exact change it will make. From the terminal:

mecca hook create <app> --kind git --provider gitlab --provision
mecca hook provision <app> <hook-id>     # for a hook you already created
mecca hook reconcile <app> <hook-id>     # check the provider's copy still matches
  • You need the right role: Maintainer or Owner on the GitLab project, admin on the GitHub repository.
  • GitLab needs extra access. A normal GitLab link is read-only. Click Grant webhook access in the dialog to re-link with the api scope. ⚠ On GitLab that scope is account-wide: it upgrades the access AppMecca holds for everything you do with GitLab, clones included — not just this project. GitHub's normal link already allows it.
  • It uses a fresh secret. Setting up the webhook mints a new secret, so the one shown when you created the hook stops working.
  • If it fails, nothing changes: the hook and its secret are as they were, and the dialog shows the reason and the manual steps below.
  • Rotating a hook AppMecca set up updates the provider first, so both sides change together; if the provider refuses, nothing changes. To rotate anyway, mecca hook rotate <app> <id> --local-only (or Rotate here only) — then update the secret on the provider by hand, or pushes are refused.
  • Revoking the hook also removes the webhook from the provider; if that fails, the hook is still revoked and you are told what to delete by hand.
  • Disconnecting your GitLab/GitHub account removes the webhooks it created; any it could not remove are listed with the manual step.
  • ⚠ Disabling a hook AppMecca set up does not touch the provider: it keeps sending pushes, and AppMecca ignores them until you re-enable the hook.

An API key cannot do this — it has no linked account; sign in with mecca login.

GitHub's default content type breaks the signature

The signature is computed over the raw request bytes, so leaving the content type on its default (application/x-www-form-urlencoded) changes what we verify and every delivery is refused — with no other symptom. Set it to application/json.

Deploy-on-push is off for every branch of a new webhook, and it only ever redeploys a branch that is already serving — a push never creates an environment. Turn it on once the webhook is added in your provider:

mecca app auto-deploy <app> -b main --on

or from the webhook's row on the dashboard's Webhooks tab, which lists every branch the webhook reaches and, for each, whether a push would deploy it. --off turns it off again. For an --any-branch webhook, turn it on for each branch you want redeployed — a push to any other branch is accepted and ignored.

Pushed and nothing happened?

Check the hook's delivery log — from the terminal:

mecca hook deliveries <app> <hook-id>

or the Deliveries view on the dashboard's Webhooks tab. It records every verified delivery including the ones deliberately skipped — a tag push, a branch delete, or a push to a branch the hook is not bound to are all logged as Ignored with the reason, and a wrong secret as Not verified. That log is almost always the answer.

Two more reasons are about the branch, not the webhook: branch_not_running (nothing is serving on it — deploy it once) and auto_deploy_disabled (deploy-on-push is off for it — the row names the command to turn it on). And duplicate_delivery means your provider sent the same push again (a retry, or you pressed Redeliver/Resend) within 15 minutes of it already starting a build — nothing is built twice.

⚠ Ignored is not a failure: it means the platform verified your request and correctly decided this push was not one it should act on.

Deliveries are kept for 30 days, and the most recent 200 per hook are kept regardless of age — so an empty log genuinely means nothing has reached the platform, rather than that the history aged out.

The build uses the branch's HEAD, not the pushed commit

AppMecca clones the branch and builds whatever is at its tip; the pushed SHA is recorded for the audit trail only. If two pushes land close together, the first build may already include the second commit.

Several pushes in a row cost about two builds

When a push arrives while earlier pushes from the same webhook are still waiting to build, those waiting builds are cancelled as superseded (the pipeline shows superseded_by=…) and only the newest runs; a build that has already started always finishes. Because every build uses the branch's latest commit, nothing is lost — you only pay for fewer builds. It never touches another webhook's, another branch's, or your own manual deploys.

Workflow: Feature Branch Testing

A typical workflow for testing a feature branch:

# 1. Build and deploy the feature branch (deploy is automatic on success)
mecca build trigger -e dev -g git@github.com:org/myapp.git -b feature/payments

# 2. Check it's running
mecca app show myapp -b feature/payments -e dev

# 3. Clone production data for realistic testing
mecca app backup myapp -b main -d "for-feature-test" -e dev
mecca app backups myapp -b main -e dev            # copy the bundle prefix
mecca app restore myapp <bundle-prefix> -b feature/payments --force -e dev

# 4. Test the feature branch (take the exact URL from step 2)
curl https://myapp-feature-payments-web-yourgroup.dev.appmecca.net

# 5. When satisfied, merge the branch to main and deploy main
#    (branch → production "promotion" is the planned blue-green feature and
#     is not yet available)
mecca app deploy myapp -b main -e dev

# 6. After merging, clean up the feature branch
mecca app undeploy myapp -b feature/payments -e dev

Limits and Considerations

Resource Limit Notes
Concurrent branch deployments ~500 per environment Limited by the host-port range (7001-8999)
Router rules 100 per listener Each branch creates 1 rule per service
Docker networks per host ~30 Limited by available Docker bridge subnets
Replicas per service 3 deploy[].replicas accepts 1-3
Declared drive size 100 GB per drive Above the free allowance your group needs a storage grant
Database name length 63 characters Hash-based names stay within limits
DNS label length 63 characters Long branch names fall back to hash-based domains

Clean up unused branches

Regularly undeploy and delete feature branches that have been merged. This frees up ports, listener rules, and Docker networks.

Command Reference

Command Description
mecca app deploy APP -b BRANCH Deploy a specific branch
mecca queue status <action-id> Check deployment status
mecca queue logs <action-id> Read the deploy log
mecca app undeploy APP -b BRANCH Undeploy a branch (preserve data)
mecca app delete APP -b BRANCH Fully purge a branch (drops its database)
mecca app list Every app/branch in the environment
mecca app show APP -b BRANCH Detail for one branch: services, health, URLs
mecca logs APP -s SERVICE -b BRANCH Logs for a branch's service
mecca var set APP KEY=value -b BRANCH Set an env var for a branch
mecca build list -a APP Build history (all branches); the Tag of a succeeded build is what you roll back to
mecca app deploy APP --image-tag <tag> --no-build -b BRANCH Roll a branch back to a known-good image

Branch → production "promotion" (mecca deploy promote / demote) is the planned blue-green feature and does not exist today.