Skip to content

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.

Edgible supports three access protocols:

typeWhat it doesWhen to use
httpsTLS terminated at Caddy on the device; HTTP forwarded to the workload.The default for any web app or HTTP API.
tcpRaw TCP byte stream forwarded over the tunnel — bytes are not inspected or modified by the platform.Databases, custom protocols, anything non-HTTP.
udpRaw 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.

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.

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 acmeadmin.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.

hostname: { custom: api.example.com } # one FQDN; rides alongside the generated name

Add 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 lifecycle
tls: { managedBy: passthrough } # workload handles TLS itself

With 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).

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 by edgible 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.

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.

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.

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] } }
  • path is /x (exact) or /x/* (prefix). Matching is longest-prefix, exact beats wildcard, and is deterministic regardless of the order you list routes.
  • stripPrefix (default false) controls the path the workload sees. With the default, GET /api/health arrives at the workload as /api/health. Set stripPrefix: true and the matched prefix is removed — GET /api/health arrives as /health.
  • policies are per-route, so /api/* can require an api-key while /* is public.
  • An access-level target is shorthand for a single /* route. You give an access either target or routes (not both — ACCESS_TARGET_OR_ROUTES). Two routes with the same path is DUPLICATE_ROUTE_PATH.
  • A missing /* route is legal (API-only apps) — edgible stack validate warns, it is not an error. Requests that match no route get a platform no-route page: HTTP 404 with header X-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.

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:

Terminal window
edgible stack deploy -f app.yml --allow-hostname-removal

Adding hostnames never trips the guard.