Access, hostnames, and TLS
An application becomes publicly reachable when it has at least one access entry. An access entry connects a port on a workload to a hostname on the internet, and chooses the protocol and the TLS strategy that bridges the two.
spec: access: - name: public type: https target: { workload: web, port: http } hostname: { generated: true } tls: { managedBy: edgible } policies: auth: { modes: [none] }This page walks through each piece.
Protocols
Section titled “Protocols”Edgible supports three access protocols:
type | What it does | When to use |
|---|---|---|
https | TLS terminated at Caddy on the device; HTTP forwarded to the workload. | The default for any web app or HTTP API. |
tcp | Raw TCP byte stream forwarded over the tunnel — bytes are not inspected or modified by the platform. | Databases, custom protocols, anything non-HTTP. |
udp | Raw UDP datagrams forwarded over the tunnel. | Game servers, DNS, anything UDP-native. |
For https, the platform handles TLS. For tcp and udp, your workload is responsible for any encryption it wants — the platform is a transparent pipe.
Hostnames
Section titled “Hostnames”A hostname tells the platform which traffic belongs to this access entry. Every https access always gets a generated hostname; a custom domain is optional and additive on top of it.
Generated
Section titled “Generated”hostname: { generated: true }Edgible mints a hostname under a platform-owned domain from your app and access names. The primary access gets the bare <app>.<org>.edgible.com; every other access gets its name as a prefix label: <accessName>.<app>.<org>.edgible.com (for example an access named admin on app myapp in org acme → admin.myapp.acme.edgible.com). The hostname is stable for the lifetime of the access — it doesn’t change on redeploy. See Multiple access entries for how primary is chosen.
The generated hostname is never withdrawn while the access exists — even after you add a custom domain, it stays live as a support/debug escape hatch.
Custom
Section titled “Custom”hostname: { custom: api.example.com } # one FQDN; rides alongside the generated nameAdd a hostname you control. custom is a single FQDN and is additive: the access serves on both the generated name and your custom name at once. That makes cutover zero-downtime — both names answer while your DNS propagates — and means an access can have up to two certificates (one for the generated name, one for the custom name).
You’re responsible for pointing DNS at Edgible: add a CNAME from your custom name to the access’s generated .edgible.com hostname (Edgible keeps that name’s A record on the gateway, so the CNAME chases it automatically). stack deploy, stack status, and application get all print the exact record as a two-line block — a label line followed by the indented record:
DNS record needed for <hostname>: <hostname> CNAME <target>Apex domains get an ALIAS/ANAME variant. The generated hostname gates the deploy; the custom hostname does not block it — once DNS resolves, its certificate is issued by the edge-cert reconciler and traffic starts flowing on that name too. If issuance failed before DNS was ready, edgible certs retry <hostname> --app <id> forces a fresh order (the platform also retries on its own within ~6h). See Use a custom domain for the full walkthrough.
To publish the same workload at several custom hostnames, declare multiple access entries (below) — one custom domain per access.
For https access, you choose how the certificate is obtained.
tls: { managedBy: edgible } # default — platform manages cert lifecycletls: { managedBy: passthrough } # workload handles TLS itselfWith managedBy: edgible, the platform orders the certificate, validates the domain, installs it on the device’s Caddy, and rotates it before expiry. You don’t see the cert; the platform handles its lifecycle. This is the default.
With managedBy: passthrough, the platform forwards TLS bytes directly to the workload — your workload terminates TLS itself. Use this when you have a specific reason to control the certificate (for example, mTLS terminated by your application).
Auth policies
Section titled “Auth policies”Every access entry can specify an authentication mode that runs before the request reaches your workload:
policies: auth: { modes: [none] }Five modes are supported:
none— anyone with the URL can reach the service. Use for genuinely public endpoints.edgible-login— the requester must present a valid Edgible session for a member of your organization. Use for internal tools.api-key— the requester must present a bearer token issued byedgible application api-keys create. Use for programmatic access.short-code— the requester must present a short-code token (rotating, time-bounded, optionally usage-capped). Use for short-lived shareable access.
The check happens at Caddy on the device, before the request reaches your workload. A missing or invalid credential returns 401 without your code seeing the request at all. See Authentication modes for the details of each.
Other policy fields
Section titled “Other policy fields”Beyond auth, an access entry’s policies block can include ipRules, rateLimit, and waf — see the YAML reference for the full set. ipRules.allow/ipRules.deny (IPv4/IPv6 addresses or CIDR blocks) are enforced at the gateway: an allow list is default-deny (only listed sources reach the app), a deny list drops listed sources, and deny wins when both match. Enforcement of WAF and rate-limiting is delivered by the platform edge and may evolve over time.
Multiple access entries
Section titled “Multiple access entries”An application can declare several access entries — a public web surface, a login-gated admin surface, a raw TCP port — each with its own hostname, TLS, and policies.
spec: workloads: - name: web type: compose composeFile: ./compose.yml ports: - { name: http, containerPort: 8080, protocol: tcp } - name: admin type: docker image: ghcr.io/acme/admin:latest ports: - { name: http, containerPort: 9000, protocol: tcp } access: - name: public # → myapp.acme.edgible.com (primary) type: https primary: true # optional; defaults to the first https entry target: { workload: web, port: http } hostname: { custom: app.example.com } # additive; generated name stays live tls: { managedBy: edgible } policies: { auth: { modes: [none] } } - name: admin # → admin.myapp.acme.edgible.com type: https target: { workload: admin, port: http } policies: { auth: { modes: [edgible-login] } }Primary selection. One https access is the primary and gets the bare <app>.<org>.edgible.com. Set primary: true on exactly one access to choose it (declaring two is rejected with MULTIPLE_PRIMARY_ACCESS); if none is marked, the first https access is primary. Every non-primary access gets the <accessName>.-prefixed name.
Agent version. Multiple access entries, routes[], and additive custom hostnames are enforced on the device, so the target device/gateway must run agent ≥ 1.2.0. An older agent fails the apply with 400 AGENT_VERSION_TOO_OLD naming the field and device.
Path routing (routes)
Section titled “Path routing (routes)”Instead of a single target, an https access can carry a routes[] table that sends different paths to different workloads, each with its own auth:
access: - name: public type: https hostname: { generated: true } tls: { managedBy: edgible } routes: - path: /api/* target: { workload: api, port: http } stripPrefix: false # default — workload receives /api/health unchanged policies: { auth: { modes: [api-key] } } - path: /* target: { workload: web, port: http } policies: { auth: { modes: [none] } }pathis/x(exact) or/x/*(prefix). Matching is longest-prefix, exact beats wildcard, and is deterministic regardless of the order you list routes.stripPrefix(defaultfalse) controls the path the workload sees. With the default,GET /api/healtharrives at the workload as/api/health. SetstripPrefix: trueand the matched prefix is removed —GET /api/healtharrives as/health.policiesare per-route, so/api/*can require an api-key while/*is public.- An access-level
targetis shorthand for a single/*route. You give an access eithertargetorroutes(not both —ACCESS_TARGET_OR_ROUTES). Two routes with the samepathisDUPLICATE_ROUTE_PATH. - A missing
/*route is legal (API-only apps) —edgible stack validatewarns, it is not an error. Requests that match no route get a platform no-route page: HTTP 404 with headerX-Edgible-Route: none, so a platform 404 is distinguishable from a workload 404 at a glance.
The older pathPolicies (path-scoped auth only) is deprecated in favour of routes[] but still accepted; declaring both on one access is rejected with ROUTES_PATHPOLICIES_CONFLICT.
Changing hostnames safely
Section titled “Changing hostnames safely”Any apply whose diff removes a live hostname — renaming or deleting an access, reassigning primary, or removing a custom domain — is refused with 409 HOSTNAME_REMOVAL_BLOCKED naming the affected hostnames. Re-run with --allow-hostname-removal to confirm:
edgible stack deploy -f app.yml --allow-hostname-removalAdding hostnames never trips the guard.