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:
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
First Deploy for a Branch¶
When you deploy a branch for the first time, AppMecca automatically creates:
- A branch-specific routing group
- A router rule for the branch URL
- A separate database (if your app uses one)
- Containers on a dedicated Docker network
- 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:
Expected output
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
--componentspicksdb,drivesand/orbuckets; the default isdbalone.- 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:
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):
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:
Your application should use this prefix when constructing cache keys:
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:
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:
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.
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
apiscope. ⚠ 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:
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:
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.