Domain Routing¶
AppMecca automatically assigns HTTPS URLs to your deployed services. This page explains how URLs are generated, how HTTPS works, and what happens with multi-service applications.
How URLs Are Assigned¶
When you deploy an application, AppMecca builds a hostname from four things:
- App slug -- derived from your Git repository name
- Service name -- the
nameof the service in yourdeploysection - Group -- the group that owns the app
- Environment domain -- e.g.
dev.appmecca.net
The result is:
For example, an app called hello-flask owned by the group acme, with one
service named web, deployed to dev:
The service name is always in the URL — even for a single-service app
There is no bare {app}.{env}.{domain} address. An app with exactly
one service still gets {app}-{service}-{group}.{env}.{domain}. If you
expected hello-flask.dev.appmecca.net to work, it will not — always read
the real URL from mecca app show <app>.
Multi-service apps¶
Each service with a port gets its own hostname, differing only in the service
segment. For example, an app named shortlink declaring services web,
fastapi, svelte, and react:
https://shortlink-web-acme.dev.appmecca.net
https://shortlink-fastapi-acme.dev.appmecca.net
https://shortlink-svelte-acme.dev.appmecca.net
https://shortlink-react-acme.dev.appmecca.net
Services without a port don't get URLs
Background workers and other services defined without a port in appmecca.yaml are internal-only. They don't receive any web traffic and don't get a URL.
Never hardcode a URL — read it or template it
Use mecca app show <app> to see every service's real URL, and
${service:name} in appmecca.yaml when one service needs another's
address. See Inter-Service Communication.
HTTPS (TLS/SSL)¶
All traffic to your application is served over HTTPS. AppMecca handles this automatically:
- AppMecca issues a wildcard TLS certificate covering
*.{env}.{domain}when the environment is provisioned - The AppMecca HTTPS router terminates TLS and forwards plain HTTP to your containers
- Your containers do not need to handle HTTPS themselves -- they just serve plain HTTP
No certificate management needed
You never need to create, renew, or manage TLS certificates. The wildcard certificate covers every app in the environment automatically, and AppMecca renews it for you.
Traffic Flow¶
Here is the full path a request takes from a user's browser to your application:
Browser
|
| HTTPS (port 443)
v
Platform DNS (*.dev.appmecca.net -> the HTTPS router)
|
v
AppMecca HTTPS router
|
| Matches the hostname -> routes to your service
v
AppMecca host (a dynamically allocated host port)
|
| Docker port mapping
v
Your Container (the container port you declared, e.g. 8000)
The router inspects the Host header of each request and forwards it to the matching service. Each service is health-checked independently.
The host port is allocated by the platform and changes
AppMecca picks a host port for each container from a reserved range and re-allocates it on every redeploy. You never configure it, and you should never assume a fixed value.
Public and Private Apps¶
Every app is private by default — a newly registered app is reachable only to members of your group, and everyone else gets an authentication challenge instead of your site. Run mecca app publish <app> to make it public, or click Make public next to the Visibility badge on the app's overview in the dashboard. Being private puts the AppMecca auth gateway in front of the app:
- an anonymous visitor gets a
302to the environment's login page, with?next=so they land back where they started - a signed-in user who is not a member of the app's group gets a
403 - members of the group pass straight through
mecca app protect myapp # private — auth-gated
mecca app publish myapp # public — opt in when you're ready
mecca app show myapp # confirm: the "Visibility" row
Both commands are idempotent and you never need to redeploy. How quickly the change reaches your visitors depends on how your app is served — see How long the flip takes, which is worth reading before your first flip. It ranges from about fifteen seconds to a few minutes.
You can also declare it in appmecca.yaml, per service:
containers:
- name: web
dockerfile: Dockerfile
deploy:
- name: web
container: web
port: 8000
visibility: private
The platform stores a single visibility flag per app and the most restrictive service wins — if any service declares visibility: private, the whole app is private. The appmecca.yaml value is applied when the app is registered; mecca app protect / mecca app publish override it afterwards.
Keep your production branch public
Private mode is meant for a non-production branch that needs gating while
you build — a dev or qa URL you want to share with a reviewer but not
with the open internet. Custom domains and edge caching only work on a
public app (see below), so the branch serving your real traffic should
stay public.
What the gateway passes to your app¶
A request that goes through the gateway reaches your app almost exactly as it was sent. That covers every request to a private app, and every request to a function service. A few request forms are refused or changed on the way:
CONNECTis refused with400and an empty body. It never reaches your app.OPTIONS *is refused with404and the plain-text bodyNot Found. AnOPTIONSrequest for a real path, such as/api/items, is handled like any other request.- A
POST,PUTorPATCHwith no body reaches your app withcontent-length: 0added. A request that already carries a length is not changed. - Your cookies are passed through, including one whose value holds non-ASCII characters. The gateway removes only the platform's own sign-in cookies, so your app never sees a visitor's AppMecca session.
There are only two app shapes — and caching decides the rest¶
Everything about visibility follows from how your app is served, and there are only two ways:
| Shape | What it is | Served from |
|---|---|---|
| Container | Anything that runs as a process — a backend, an API, a worker, or a server-side-rendered frontend. An ordinary service (and type: ssr). |
Your container, behind the HTTPS router |
| Static | Files built once and uploaded — type: static, and type: spa builds. No process runs. |
AppMecca object storage, behind the edge cache |
The distinction that then matters is whether the edge cache sits in front of your hostname, because the edge cache is a second front door and the auth gateway only guards the HTTPS router.
- A static app always has one. There is no other way to serve it, so edge caching is inherent — you do not switch it on and you cannot switch it off. The default lifetime for a cached file is 24 hours.
- A container app has one only if you asked for it, with a
cache:block on the service. Without that block your hostname points straight at the HTTPS router and nothing is cached anywhere.
That gives three cases, and they behave differently:
Container, no cache: block Browser -> HTTPS router -> gateway -> your container
Container, cache: block Browser -> edge cache -> HTTPS router -> gateway -> container
Static (always) Browser -> edge cache -> object storage
Container without a cache: block — the simple case¶
Going private re-points the routing rule for your hostname at the gateway. Nothing else about how the app is served changes, no DNS record moves, and nothing is cached anywhere that could outlive the change. This is the fastest and cleanest flip: about fifteen seconds, and there is no window in which the old behaviour lingers.
Container with a cache: block — caching is suspended while private¶
A cached response is served from the edge without contacting your app or the gateway — which is exactly why caching and gating cannot both apply to the same path. So while the app is private, the platform suspends your caching for you: every path is switched to pass-through, so every request reaches the gateway with cookies and headers intact, and the cache is cleared. Going public restores exactly the caching you declared.
You do not have to configure anything for this, and you do not have to choose between the two. Two consequences are worth knowing:
No edge caching while private, and a short window on the flip
A private app is not edge-cached, deliberately — caching an authenticated response at a shared edge is the thing the auth gate exists to prevent. It is served from the environment's region instead, so a private app is measurably slower than the same app public. Private static sites behave the same way, for the same reason.
Clearing the cache is not instantaneous. Changing the caching rules does not by itself evict what is already stored, so the flip also clears the cache — and that takes a couple of minutes. Until it finishes, a path that was cached while the app was public can still be served from the edge. If you need something unreachable immediately, undeploy it rather than making it private.
The simplest configuration avoids all of this
If no path has a cache lifetime, none of the above applies — there is nothing to suspend and nothing to clear:
cache:
enabled: true
default_ttl: 0 # the default — nothing is cached
# and no `behaviors:` entries, because a behaviour's `ttl`
# defaults to 86400 (24 hours) when you omit it
That is also what a bare cache: {enabled: true} gives you. It passes
every request through to your app, which is what most apps want.
Static — the edge cache is taken out of the path¶
A static app is always served through the edge cache. Going private takes the edge cache out of the request path entirely — the DNS record for your hostname moves from the edge cache to the HTTPS router, and a request is then served like this:
Browser
|
| HTTPS
v
Platform DNS (myapp-web-mygroup.dev.appmecca.net -> HTTPS router, not the edge)
|
v
AppMecca HTTPS router
|
v
Auth gateway --> 302 to login if you have no session
| 403 if you are not in the app's group
| (authenticated)
v
Your built files, read from private AppMecca object storage
Moving the DNS record is not enough on its own, because the edge cache would still answer for anyone who reaches it directly — so while the app is private the platform also switches the edge cache off for that hostname. That is what makes the gateway the only way in, which is the guarantee a container app gets for free.
It is switched off, never deleted, and your built files stay where they are. mecca app publish myapp switches it back on, waits for it to be ready, and only then moves DNS back to it — in that order, so your visitors are never sent to an edge cache that is not serving yet. You never rebuild or redeploy to get it back.
Because the edge cache is bypassed, everything attached to it is bypassed with it. That produces two consequences worth understanding before you flip a static app.
1. No edge caching while private¶
Every request travels browser → HTTPS router → gateway → object storage, and is served from the environment's own region rather than from a location near your visitor. Responses are also marked Cache-Control: no-cache, so browsers revalidate instead of reusing what they already have. A private static site is measurably slower than the same site served publicly.
This is deliberate, not a limitation waiting to be lifted: caching an authenticated response at a shared edge is exactly the thing the auth gate exists to prevent. If you need edge performance, the app needs to be public.
Directory-style URLs stop resolving while private
Serving through the edge cache includes a small function that rewrites
/page/ to /page/index.html. That function lives on the edge, so
private mode bypasses it too. A site whose generator emits
directory-style URLs (MkDocs with its default use_directory_urls, Hugo,
Jekyll) will serve / fine but 404 on sub-pages while private. Sites
with spa_fallback enabled — React, Vue, Svelte builds — are unaffected,
because the fallback re-serves the entry document for any unmatched path.
2. No custom domains on a private app¶
For a static app, a custom domain's certificate and alias attach to the edge cache — the thing private mode bypasses. So the platform keeps the two mutually exclusive, in both directions:
mecca domain addon a private app is refused with a409, telling you to runmecca app publish <app>first. This applies to any private app, container or static.mecca app protectis refused with a409while the app has any active custom domain.
If you genuinely want to drop the domains and go private, say so explicitly:
That starts teardown of each attached custom domain and then performs the flip. It is destructive — the domains are gone, and re-adding them after you go public again means re-doing the DNS records and waiting for a fresh certificate. There is no equivalent flag on mecca app publish; going public never needs to destroy anything.
If the URL still looks public¶
A static app can only be flipped once it has actually been deployed — the platform needs a live edge cache to park and a set of built files to serve. If you run mecca app protect on a static app that has never had a successful deploy, there is nothing to re-point and the URL keeps serving as before. Deploy first, then flip.
To re-assert the current visibility without changing it:
How long the flip takes¶
A container app with no cache: block changes in about fifteen seconds — up to a minute if the gateway's routing table has only just refreshed — and the rest of this section does not apply to it: no DNS record moves, so there is nothing for a visitor's machine to remember.
A static app is different, and the difference is not a fault. Its flip moves a DNS record, and DNS answers are cached — by resolvers, and by browsers, which keep their own cache independent of the record's 60-second lifetime. So for a few minutes after the flip, two visitors can legitimately see two different things: anyone resolving the name fresh gets the new behaviour immediately, while anyone whose machine already looked it up keeps the old behaviour until their cache expires.
That includes you. You are the most likely person to have visited the site just before flipping it, so you are the most likely to see the stale view — which is why it reads like a broken deploy when it is not.
The two directions produce opposite symptoms:
| You flipped | Someone with a stale DNS answer sees | Why |
|---|---|---|
| public → private | the site still loading normally | their machine still points at the edge cache, which serves for a short while longer |
| private → public | a login prompt, on an app that is now public | their machine still points at the HTTPS router, where the gateway is still challenging |
Neither is fixable from our side — the visitor's own resolver is holding an address we no longer want it to use. Both clear themselves.
What to do: wait two or three minutes and reload. If you want to see the change immediately in your own browser, clear its DNS cache — in Chrome, open chrome://net-internals/#dns and choose Clear host cache. A hard reload alone will not help, because the stale entry is a DNS answer, not a cached page.
Going private is not instantaneous, on purpose
When you make a static app private the platform moves DNS first and only switches the edge cache off once visitors have had time to notice — roughly fifteen minutes. Switching it off sooner would give anyone with a cached address a hard error instead of the site, and an error is a worse outcome than briefly still serving content that was public a moment ago. Access through the gateway is gated from the moment the flip lands; the delay only affects visitors who have not yet re-resolved the name.
Do not use a visibility flip as an incident response
If content must become unreachable right now — the wrong data shipped,
a leak — do not rely on going private, for any app shape. Undeploy it
(mecca app undeploy myapp), which stops serving at the source rather
than changing where visitors are pointed.
The gateway itself is always public
mecca app protect gateway is refused. The gateway is what authenticates
everyone else; gating it behind itself would produce a login loop.
Custom Domains¶
Custom domains are additive — your service is accessible on BOTH the system-generated URL (myapp-web-acme.dev.appmecca.net) AND your custom domain (www.example.com). The system domain never goes away.
Custom domains require a public app
mecca domain add is refused with a 409 on an app whose visibility is
private, and mecca app protect is refused while any custom domain is
active. See Public and Private Apps for why,
and for the --force-remove-domains escape hatch.
Adding a Custom Domain¶
Custom domains are managed exclusively via CLI or API. This ensures the full certificate lifecycle (issuance, DNS validation, attaching the certificate to the router or edge cache) is handled correctly — these steps cannot be driven from a config file alone.
# Add a custom domain to a service (-s is required)
mecca domain add myapp www.example.com -s web -e dev
# Check certificate validation status
mecca domain verify myapp www.example.com -e dev
# List all custom domains
mecca domain list myapp -e dev
# Remove a custom domain
mecca domain remove myapp www.example.com -e dev
The app name comes first, then the domain; the service is the -s/--service
option and is required on add.
The domain / domains fields in appmecca.yaml do nothing
Both fields are accepted by the schema for forward compatibility, but they
are silently ignored during deployment — nothing warns you at deploy
time that they had no effect. Custom domains must be added via
mecca domain add so that certificate issuance and DNS validation can
actually happen.
Unknown keys in appmecca.yaml are ignored silently
This is not specific to domains. AppMecca ignores any key it does not
recognise anywhere in appmecca.yaml — no error, no warning. A typo like
helth_check: or replica: simply does nothing, and the service deploys
with the default. Double-check spelling against
Configuration: Services when a setting
appears to have no effect.
How It Works — the uniform two-record procedure¶
Every custom domain needs exactly one traffic record + one validation record:
Add these TWO DNS records at your domain provider:
1. Traffic:
www.example.com CNAME myapp-web-mygroup.dev.appmecca.net
2. Certificate validation (one-time):
_acme-challenge.www.example.com CNAME _acme-challenge.www.example.com.acme-delegated.dev.appmecca.net
The validation record delegates certificate validation to the platform
once: the TLS certificate is issued automatically — even before you
flip traffic — and renews forever with no further DNS changes.
mecca domain verify <app> <domain> live-checks both records and tells you
exactly which one is still missing.
Both records — with the record type correct for your domain (CNAME,
or A/ALIAS at an apex) — are shown on the dashboard's Domains
tab, each with Copy name and Copy value buttons and a
Check DNS button that live-resolves it.
Copy the record NAME with the button — don't hand-select it
A record name pasted with an invisible extra character (most often a
leading space picked up by a mouse selection) is stored by your DNS
provider under a different name that renders identically in the
console — Route 53, for example, stores it as \040_acme-challenge.…
and shows no visible difference. The record then never resolves,
while your provider truthfully shows it as present. Use the Copy
name button (dashboard), or copy from the flush-left record lines
the CLI prints. If it happens anyway, Check DNS / mecca domain
verify will detect the common mispastes and name the
nearly-identical record it found, with the exact fix: delete it and
recreate it with the name copied exactly.
A record that says \"not resolving yet\" but the cert issues anyway
Certificate issuance asks your domain's authoritative nameservers. A public resolver may still be serving a cached "does not exist" answer from before you added the record (DNS negative caching, up to 24 h with some providers). When AppMecca sees that shape it says so explicitly — "correct at your nameservers; a stale DNS cache is clearing" — and proceeds with issuance. Nothing to do but wait for the cache to expire; your visitors on other resolvers are unaffected.
Custom domains live on the production branch¶
A custom domain belongs to the project, not to one branch — and it is
served by your production branch (the one marked with
mecca app set-production; main by default). So custom domains are
viewed and added only from that branch:
- The dashboard's Domains tab shows the System hostnames for every branch — each branch has its own automatic hostname, which is how you preview a feature branch.
- The Custom domains section and the Add domain form appear only on the production branch. On any other branch you'll see a note telling you which branch to switch to.
- The API refuses a custom-domain write aimed at a non-production branch
with a
409.
Promoting a different branch does not move your domain yet
Today a custom domain is served by main. Running
mecca app set-production on another branch updates the flag but does
not re-point your live custom domain — that is coming (it is a
tracked feature). Until then, keep your production branch as the one
your domain should serve.
Apex (root) domains¶
DNS forbids CNAME at the apex (example.com), so the traffic record
forks by what your DNS provider supports — the validation record stays a
normal CNAME everywhere:
| Your DNS provider | Traffic record at the apex |
|---|---|
| Supports ALIAS / ANAME / CNAME-flattening (Cloudflare, Namecheap, Porkbun, DNSimple, Gandi, DNS Made Easy, easyDNS, Squarespace DNS) | ALIAS example.com → {platform hostname} — apex serves directly |
| No ALIAS support (GoDaddy, OVH, IONOS, Hostinger, most registrar-default DNS) | A example.com → {the environment's fixed ingress IPs} — printed by mecca domain add |
The exact record mecca domain add prints is the authoritative one — it is
computed for your provider, your app shape, and your apex policy.
Choose what the apex does¶
An apex can redirect to www. or serve your site itself. You
pick; we recommend redirecting.
mecca domain add myapp example.com -s web # 301 → www (recommended)
mecca domain add myapp example.com -s web --apex serve # answers at example.com
mecca domain set-apex myapp example.com serve # change your mind later
Redirect gives you one canonical address (better for SEO — no
duplicate-content split), one certificate path, and it works with
every DNS provider. It is what Vercel, Netlify and Render all
recommend. When you choose it, mecca domain add example.com
automatically sets up the www.example.com sibling and prints its
records too.
Serve answers directly at the root. For a container app it just
works. For a static site your DNS provider must support ALIAS/ANAME —
the content is served through the edge cache, whose addresses are not stable,
so an A record cannot reach it whatever it points at. If your registrar has
no ALIAS support (GoDaddy and friends), choose redirect or move DNS to us
with mecca domain ns-setup.
Defaults, if you say nothing: a static site redirects (it is the only thing it can do without ALIAS); a container serves at the apex.
Changing the policy is a visible cutover
mecca domain set-apex makes the redirect appear or disappear, and
the apex traffic record changes shape (A ↔ ALIAS). The domain
returns to "waiting for DNS" until you republish it; the certificate
then re-issues automatically. Your www. host is never touched.
Certificate auto-renewal
Certificates renew automatically as long as the _acme-challenge
validation CNAME stays in place. You never need to manually renew,
and renewals never require new DNS records.
Cloudflare users
If your domain is proxied through Cloudflare (orange cloud), set SSL/TLS mode to Full — "Flexible" causes redirect loops.
Full DNS delegation (NS hosting)¶
If you'd rather AppMecca manage all of your domain's DNS — so the apex Just Works and you never touch a DNS record again — delegate your domain's nameservers to us once. This is a chargeable add-on best for apex-heavy setups or customers who want a single place to manage everything.
# 1. Scan your existing DNS and stage the import (nothing changes at
# your registrar yet). Prints the 4 nameservers to set.
mecca domain ns-setup example.com --force
# 2. Watch for activation (we detect when your registrar's NS flips).
mecca domain ns-status example.com
# Later, to hand DNS back to another provider:
mecca domain ns-release example.com
Your email keeps working. Before you change a single nameserver,
ns-setup scans your live DNS — every MX, SPF/TXT, DKIM, and
A/CNAME/SRV/CAA record — and imports it into the AppMecca zone
first, so when you flip the NS at your registrar the switch is
seamless. Review the imported record list ns-setup prints before you
change anything; anything unusual (a rarely-used subdomain, a
non-standard DKIM selector) you can add afterward with the platform once
delegated.
Once your registrar's nameservers point at us and we detect the flip
(ns-status shows active), certificates issue and renew with zero
DNS records on your side — validation happens entirely inside your
AppMecca-hosted zone.
Change nameservers only after the import completes
Wait until mecca domain ns-status example.com shows the imported
record set and hands you the four nameservers. Flipping NS before
the import lands is the one way to drop email during a migration.
ns-status works from any of your groups
A domain is globally unique and belongs to one group, so
ns-status authorizes you by membership of that group — you no
longer have to switch your active group to check it. Previously it
replied "Domain 'example.com' is not NS-hosted." to the domain's
own owner if their active group was a different one of their own,
which was not merely unhelpful but untrue.
ns-setup and ns-release are deliberately not the same. Both
write ownership — one stamps it, the other irreversibly deletes the
zone — so they still act on your active group and want an explicit
--group when you have more than one.
URL Patterns at a Glance¶
Every hostname carries the service name; the branch segment appears only on a non-production branch.
| Scenario | URL Pattern | Example |
|---|---|---|
| Any service, production branch | {app}-{service}-{group}.{env}.{domain} |
myapp-web-acme.dev.appmecca.net |
| Multi-service (frontend) | {app}-{service}-{group}.{env}.{domain} |
myapp-frontend-acme.dev.appmecca.net |
| Multi-service (backend) | {app}-{service}-{group}.{env}.{domain} |
myapp-backend-acme.dev.appmecca.net |
| Non-production branch | {app}-{branch}-{service}-{group}.{env}.{domain} |
myapp-feature-payments-backend-acme.dev.appmecca.net |
| Custom domain (additive) | System URL + custom domain | myapp-web-acme.dev.appmecca.net AND www.example.com |
Branch names are sanitised for use in URLs: / becomes -, uppercase becomes lowercase, and special characters are replaced with -. See Branch Deployments for details.
Don't derive it — read it
Group slugs, branch sanitisation, and reserved hostnames all feed into the
final name. mecca app show <app> prints the real URL for every service;
treat that as authoritative over any pattern in this table.
Environments and Isolation¶
Each environment has its own set of URLs. The same app deployed to different environments gets different URLs:
dev: https://myapp-web-acme.dev.appmecca.net
staging: https://myapp-web-acme.staging.appmecca.net
prod: https://myapp-web-acme.prod.appmecca.net
These are completely isolated -- separate routers, separate hosts, separate databases. Deploying to dev has no impact on staging or prod.
Troubleshooting Routing¶
App deployed but URL not working¶
-
Check that the deployment completed.
To see everything in flight for an app instead:mecca queue statustakes the action ID the deploy printed, not an app name: -
Confirm the service is healthy and check the URL AppMecca actually assigned:
-
Allow 30-60 seconds after deployment for DNS propagation and for health checks to mark the new container healthy.
-
If the URL you tried has no service segment (
myapp.dev.appmecca.net), that is the problem — see How URLs Are Assigned.
Wrong service responding¶
If you're hitting one service's URL but getting another service's response, check that your services have different health check paths and that your appmecca.yaml service names are correct.
Next Steps¶
- Inter-Service Communication -- Connect your services to each other
- Service Management -- Check status, view logs, restart services
- Configuration: Services -- Full reference for the deploy section