Local Multi-Domain Development
The single-port multi-tenant model needs a base domain with a wildcard so the operator host and the tenant hosts resolve. Locally this works with no DNS setup at all. To test with a real phone wallet on a separate device, expose the stack on a public wildcard domain. This page covers both.
The loopback default in the first section uses the Enterprise Development Kit Deployment repository: the Compose stack and the single-port Traefik gateway under compose/, with the local wildcard certificate from scripts/gen-local-wildcard-cert. For phone-wallet testing, bring your own tunnel or public DNS/TLS setup while keeping the same gateway contract.
Default: saas.localtest.me, No DNS
The default base domain for local runs is saas.localtest.me. The localtest.me domain and all of its subdomains resolve to 127.0.0.1 without any host-file edits or a local DNS server. So both the operator host and the tenant hosts work immediately:
https://platform.saas.localtest.meis the operator host.https://acme.saas.localtest.me,https://globex.saas.localtest.me, andhttps://initech.saas.localtest.meare the three default tenants.
This is enough for browser-based development on the same machine that runs the stack. Follow Install on Docker to bring it up behind the Traefik gateway, generate the local wildcard certificate, and trust the local CA. Because localtest.me resolves to loopback, this path does not reach a phone or another device.
Public Overlay for a Real Phone Wallet
A real wallet on a phone needs two things the loopback default cannot give it: public DNS that resolves from the phone, and a certificate the phone trusts. The wildcard requirement does not change: the public domain still needs platform.<base-domain> and *.<base-domain> to reach the operator host and the tenant hosts on one port. Two tunnels provide this.
Configure your tunnel to terminate or pass through TLS for platform.<base-domain> and *.<base-domain>, forward traffic to the local gateway on 443, and preserve the original Host header. Set the deployment base domain in the deployment repository environment to the public base domain before bringing the stack up, then run the normal Compose files.
ngrok Wildcard
An ngrok wildcard endpoint gives a public *.{subdomain}.ngrok.dev style domain that tunnels to the local gateway on 443. Point the deployment base domain at the ngrok wildcard so the operator host and tenant hosts resolve publicly, and ngrok terminates TLS with a publicly trusted certificate, so the phone wallet trusts it with no local CA import. A reserved wildcard domain keeps the host stable across runs, which matters because issued credentials and advertised issuer URLs embed the host.
Cloudflare Tunnel
A Cloudflare Tunnel maps a wildcard hostname on a domain you control to the local gateway. Cloudflare serves a publicly trusted certificate for the wildcard, so the phone wallet trusts the connection without importing the local CA. Configure the tunnel to forward to the gateway on 443 and preserve the Host header so tenant resolution still reads the right tenant.
Either overlay keeps the single-port model intact: the tunnel is the public front, and behind it the gateway routes by host and path exactly as in Install on Docker.
Tier 3: Your Own Domain plus Let's Encrypt
The tunnel overlays put someone else's front in the path. If you control a domain and can point its public DNS at this machine, Traefik can obtain and renew a publicly trusted Let's Encrypt certificate directly, with no tunnel. The single-port model is unchanged: the operator host is platform.<base-domain>, tenants are *.<base-domain>, the tenant is identified by the inbound Host, and the gateway preserves that Host end to end.
Configure Traefik, your gateway, or your DNS provider to obtain certificates for the operator host and tenant hosts. For arbitrary tenants, use DNS-01 and issue a wildcard certificate for *.<base-domain> plus platform.<base-domain>. If you use per-host certificates, issue certificates for every tenant host before exposing that tenant.
The operator host platform.<base-domain> and the wildcard *.<base-domain> must both resolve publicly to this machine's external IP.
Certificate Modes
Two certificate models work:
- Per-host certificates for the operator host and each named tenant host. This is useful for a fixed demo tenant set, but every new tenant needs certificate issuance before it can go live.
- A wildcard certificate for
*.<base-domain>plus the operator hostplatform.<base-domain>. This is the recommended model for arbitrary tenant subdomains. Use DNS-01 validation with your DNS provider when using ACME, because HTTP-01 cannot validate a wildcard SAN.
Pointing DNS at This Machine
Public DNS for platform.<base-domain> and *.<base-domain> must resolve to this machine's external IPv4 (an A record) and, if used, external IPv6 (an AAAA record). Keep any CDN or proxy mode off (orange-cloud off on Cloudflare) so the gateway sees the raw Host and terminates TLS itself. Tenant resolution reads the unrewritten Host, and TLS-ALPN requires the gateway to own the TLS handshake.
If you run dynamic DNS, update the records before starting the public test and confirm they resolve from outside your network.
Port Forwarding
Forward inbound TCP 443 from the external IP to this machine. The two challenge modes differ in what 443 is for:
tls-alpnrides443for both issuance and serving, so443is all it needs.dnsvalidates over DNS, so443is needed only to serve traffic, not to issue the certificate.
Forward :80 only if you want the web to websecure HTTP-to-HTTPS redirect reachable from outside. Neither challenge needs :80 for issuance, because this option does not use the HTTP-01 challenge.
The box reaching its own public hostnames through NAT needs router hairpin (loopback) support, which many home routers lack. If hairpin is unavailable, add a local hosts or dnsmasq entry mapping platform.<base-domain> and the tenant hosts to 127.0.0.1 or the box's LAN IP. The Let's Encrypt certificate stays valid either way, because certificate validity does not depend on how the name resolves locally. In-compose service-to-service calls already work through the Traefik network aliases, and because the certificate is publicly trusted, the services validate the gateway TLS with the JVM default truststore. This mode mounts no private CA truststore, unlike the self-signed overlay.
Staging First
When you use ACME automation, run against the ACME staging endpoint first to validate DNS and reachability without burning production rate limits. Switch to the production endpoint only after the gateway serves the staging certificate correctly.
Public Exposure Warning
This option binds the machine to the public internet on 443 and exposes the operator console and every tenant endpoint publicly. Use a disposable test domain, keep the machine patched, restrict the router forward to 443 only, and tear the stack down when you are done.
CA Trust
How you handle CA trust depends on the front:
- With the loopback default and a self-signed local CA, import
local-ca.crtinto the device's trust store, or runmkcert -installwhenmkcertissued the certificate. See Install on Docker. - With an ngrok wildcard or a Cloudflare Tunnel, the public front terminates TLS with a publicly trusted certificate, so no local CA import is needed on the phone.
- With your own domain plus Let's Encrypt, Traefik serves a publicly trusted certificate directly, so no local CA import is needed on the phone.
When the public overlay terminates TLS itself, the local wildcard certificate is no longer on the public path. It still serves any direct browser access on the loopback host during the same run.
Local Equals Production
The host scheme, the gateway contract, and the routing table are the same locally and in production. The only differences are the base domain and where TLS terminates. A flow that works at https://acme.saas.localtest.me works the same way at https://acme.example.com, because both resolve the tenant from the Host header and route by the same paths. See the deployment architecture for the contract that holds across every environment.
Next Steps
- Install on Docker: the local gateway and certificate setup.
- Provisioning and onboarding: onboard a platform with three fully deployed tenants.
- Deployment architecture: the host scheme and routing table.