domains
1ctl domains is the user-facing CLI surface for app hostnames. 1ctl domain is an alias for the same command group.
A domain workflow crosses several systems at once:
you └─ 1ctl domains add api.example.com --app my-api └─ Satusky API ├─ backend domain / ingress metadata ├─ Kubernetes HTTPRoute or Ingress ├─ DNS record state └─ TLS certificate stateThe CLI should hide most of that machinery during the happy path, but it must surface each layer when something is wrong. Domain management is only truly complete when the hostname recorded by the backend, the hostname routed by Kubernetes, the DNS answer on the public internet, and the certificate served over HTTPS all agree.
For the broader v1 product contract, see 1ctl v1 Contract.
Product boundary: domains own certificates
Section titled “Product boundary: domains own certificates”Satusky should keep TLS under the domain workflow rather than introduce a public first-class 1ctl cert command today.
The current CLI already points in this direction: the old issuer command is hidden because cert-manager Issuer objects are implementation details, and custom-domain TLS is intended to be provisioned automatically when a domain is attached.
| User concern | Recommended CLI home | Why |
|---|---|---|
| Add or remove a hostname | 1ctl domains ... | The user is managing reachability for an app. |
| See whether HTTPS is ready | 1ctl domains check ... | TLS readiness is part of whether a domain works. |
| Show required DNS/TLS setup | planned 1ctl domains setup ... | DNS and certificate validation are one user journey. |
| Operate cert-manager Issuers directly | not a public v1 concern | That is backend implementation detail, not product vocabulary. |
A separate public 1ctl cert command would only become justified later if Satusky intentionally supports user-operated certificate features such as:
- importing custom certificates,
- choosing between managed and user-supplied certificates,
- manual certificate rotation,
- certificate inventory independent from app domains.
Until then, splitting domains and certificates into separate top-level concepts would make the CLI more technical without making the user more capable.
Implementation status
Section titled “Implementation status”| Area | Status | Notes |
|---|---|---|
| List domains | Implemented | 1ctl domains list shows domain rows known to the current organization. |
| Add domain | Implemented, but correctness gaps remain | 1ctl domains add <domain> --app <app> exists, but the current update path appears to discard explicit custom-domain changes on apps that already have an ingress. Tracked in satusky-core_backend#340. |
| Remove domain | Implemented | 1ctl domains remove <domain> --app <app>. |
| Check domain | Partially implemented | Currently metadata-only. It should become a real diagnostic command. Tracked in 1ctl#43 and satusky-core_backend#338. |
| Route reconciliation | Known gap | Backend metadata and Kubernetes route state can drift. Tracked in satusky-core_backend#337. |
| Exact setup instructions | Not yet implemented | No domains setup flow yet. Tracked in 1ctl#48. |
Aliases and www redirects | Backend exists, CLI missing | Backend alias handlers exist, but 1ctl domains does not expose them yet. Tracked in 1ctl#49. |
| Canonical domain architecture | Known architecture gap | Legacy ingress-domain and newer alias-domain models coexist. Tracked in satusky-core_backend#342 and 1ctl#50. |
| Partial-success handling | Known correctness gap | Some backend mutations can return success after route/TLS reconciliation fails. Tracked in satusky-core_backend#341. |
| Routing primitive consistency | Mixed architecture today | Default/generated domains use Gateway API HTTPRoute, but custom domains still use legacy Kubernetes Ingress. Tracked in satusky-core_backend#343 and satusky-core_backend#344. |
Current commands
Section titled “Current commands”1ctl domains list1ctl domains add <domain> --app <app> [--port <port>] [--custom-dns]1ctl domains remove <domain> --app <app> [-y]1ctl domains check <domain>1ctl domains list
Section titled “1ctl domains list”1ctl domains listLists domains known to the current organization.
Example
DOMAIN APP TLS CREATEDhappygiraffe-3u7lttk.satusky.com diagnosis-bot platform 5 minutes ago1ctl domains add
Section titled “1ctl domains add”1ctl domains add <domain> --app <app> [--port <port>] [--custom-dns]Attaches a domain to an app through the current CLI path.
| Flag | Default | Description |
|---|---|---|
--app | required today | App name, matching [app].name in satusky.toml or --name on deploy. |
--port | 8080 | Target service port. |
--custom-dns | false | Use Let’s Encrypt TLS for non-*.satusky.com hostnames. |
Examples
1ctl domains add api.example.com --app my-api --custom-dns1ctl domains add internal.example.com --app internal-api --port 3000 --custom-dnsCurrent limitation. The installed CLI currently calls the generic ingress upsert path. In backend controllers/ingress_controller.go, UpsertIngress preserves the existing hostname on update by assigning metadata.DomainName = existingIngress.DomainName. That means an explicit custom-domain request on an app that already has a generated hostname appears likely to be ignored instead of applied. This is tracked in satusky-core_backend#340.
1ctl domains remove
Section titled “1ctl domains remove”1ctl domains remove <domain> --app <app> [-y]Removes a domain from an app.
| Flag | Default | Description |
|---|---|---|
--app | required today | App name, matching [app].name in satusky.toml. |
--yes, -y | false | Skip the confirmation prompt. |
Example
1ctl domains remove api.example.com --app my-api -y1ctl domains check
Section titled “1ctl domains check”1ctl domains check <domain>Checks the platform’s current recorded view of a domain.
Current output
Domain api.example.comApp: my-apiNamespace: orgv1-80c8588bTLS: Platform-managed (*.satusky.com)Created: 5 minutes agoCurrent limitation. Despite the command name, the current implementation in 1ctl/internal/commands/domains.go only renders ingress metadata. It does not yet show route attachment, DNS propagation, certificate status, or public reachability.
Current routing architecture
Section titled “Current routing architecture”The backend is not yet Gateway API-only. The code currently uses two Kubernetes routing primitives:
| Hostname type | Current Kubernetes resource | Evidence in backend code |
|---|---|---|
Default / generated *.satusky.com domains | Gateway API HTTPRoute when INGRESS_MODE=gateway-api | gatewayRoutingConfigForDefaultDNS, buildHTTPRouteSpec, and services/network/httproute.go |
Custom domains, aliases, and www redirects | legacy Kubernetes Ingress | DnsConfigCustom always falls through to the Ingress + cert-manager path, upsertCustomK8sIngress, buildCustomIngressSpec, and handleWWWRedirect |
current routing split default platform domains ──► HTTPRoute custom domains ──► legacy IngressThat mixed architecture is real and should be documented plainly. If Gateway API is the chosen platform standard, the legacy Ingress path should be treated as a compatibility bridge, not the desired long-term model.
Planned target, not implemented yet:
future routing model default generated hostname ──► HTTPRoute custom domain aliases ──► HTTPRoute TLS ──► Gateway-compatible certificate flow Kubernetes Ingress ──► deprecated compatibility path, then removedThe migration work is tracked in satusky-core_backend#343. Formal deprecation/removal of the legacy Ingress path is tracked in satusky-core_backend#344.
Intended architecture
Section titled “Intended architecture”A durable domain system needs one coherent source-of-truth model across four planes:
| Plane | Responsibility | Example question |
|---|---|---|
| Backend metadata | Ownership and desired attachment | Which app owns api.example.com? |
| Routing state | Kubernetes HTTPRoute or Ingress | Is that hostname actually routed to the app? |
| DNS state | Public record resolution | Does the hostname resolve to Satusky’s edge? |
| TLS state | cert-manager or platform-managed TLS | Can HTTPS be served correctly for that hostname? |
backend desired state │ ▼route reconciliation ──► DNS expectation ──► TLS issuance │ │ │ └──────── domains check should report all four ────────┘The user should not have to know which subsystem owns each plane on a good day. But when one layer disagrees with another, the CLI should expose the disagreement instead of collapsing everything into a misleading success badge.
Current architecture gaps
Section titled “Current architecture gaps”1. Explicit custom-domain updates can be discarded
Section titled “1. Explicit custom-domain updates can be discarded”The normal post-deploy workflow is supposed to be:
1ctl domains add api.example.com --app my-apiBut the current backend update path appears to overwrite the requested hostname with the existing hostname whenever the app already has an ingress. That makes the add/update workflow unreliable for the common case of attaching a custom domain after the app already has its generated default domain.
Tracked in satusky-core_backend#340.
2. Metadata and route state can drift
Section titled “2. Metadata and route state can drift”A verified failure showed this mismatch:
| Plane | Hostname |
|---|---|
| Backend domain record | happygiraffe-3u7lttk.satusky.com |
Kubernetes HTTPRoute | silentgiraffe-6z2enyu.satusky.com |
The old hostname remained live and routed traffic to the new pod, while the new hostname did not resolve. The platform must treat the database record and the actual Kubernetes route as a reconciled unit, not two facts that can silently diverge.
Tracked in satusky-core_backend#337.
3. Mutation success does not yet prove reconciliation success
Section titled “3. Mutation success does not yet prove reconciliation success”The newer alias/custom-domain backend path already contains useful functionality, but several handlers can insert or remove database rows, then log a warning if the Kubernetes Ingress or cert-manager Issuer update fails, and still return ordinary success to the caller.
A domain mutation should either:
- finish all required write-side reconciliation before returning success, or
- return an explicit operation state such as
pending,reconciling, orfailedthat users can inspect later.
It should not silently tell the user everything worked when only the database write worked.
Tracked in satusky-core_backend#341.
4. Two domain models currently coexist
Section titled “4. Two domain models currently coexist”The backend currently contains both:
- a legacy primary-hostname ingress model, and
- a richer newer model with owned domains, aliases, DNS records, and optional
wwwredirects.
The richer model appears closer to the right long-term product shape, but the current CLI mostly drives the older ingress-oriented path. That split is expensive because it lets different callers choose different semantics for the same user concept.
Recommended direction:
default generated hostname = platform routecustom domains = aliases attached to that app routeDNS / TLS / redirects = reconciled from the canonical domain modelTracked in satusky-core_backend#342 and 1ctl#50.
5. Custom domains still depend on legacy Kubernetes Ingress
Section titled “5. Custom domains still depend on legacy Kubernetes Ingress”Even with INGRESS_MODE=gateway-api, only DnsConfigDefault routes move to Gateway API today. Custom domains explicitly fall back to the older Ingress + cert-manager path, and alias / www redirect helpers still create networkingv1.Ingress objects.
This means custom-domain work currently carries both architectural migrations at once:
- unify the domain data model, and
- migrate the routing primitive from
IngresstoHTTPRoute.
Recommended direction:
custom domains should become Gateway API-nativelegacy Kubernetes Ingress should be deprecated after migrationTracked in satusky-core_backend#343 and satusky-core_backend#344.
What still needs to be implemented
Section titled “What still needs to be implemented”domains check should become a real diagnostic command
Section titled “domains check should become a real diagnostic command”Planned, not implemented yet. A complete check should report each plane separately:
| Check | Why it matters |
|---|---|
| Backend ownership | Confirms the domain is registered to an app and namespace. |
| Route attachment | Confirms an HTTPRoute or Ingress exists for the same hostname. |
| DNS records | Confirms the hostname resolves to the expected edge target. |
| TLS state | Confirms cert-manager or platform-managed wildcard TLS is ready. |
| Public probe | Confirms https://<domain>/ actually reaches the app. |
Target output shape
Domain: api.example.comBackend: attached to my-api in orgv1-80c8588bRoute: attached to HTTPRoute/my-api-routeDNS: resolves to 172.66.40.108, 172.66.43.148TLS: readyHTTP: reachableTracked in 1ctl#43 and satusky-core_backend#338.
domains setup should print exact next steps
Section titled “domains setup should print exact next steps”Planned, not implemented yet. After attaching a custom domain, users should not be told only to “point an A record at the platform LoadBalancer IP.” The CLI should print the actual required DNS records and current validation state.
Target command
1ctl domains setup api.example.comTarget output shape
Domain: api.example.comApp: my-api
Required DNS records: A api.example.com 203.0.113.10
DNS: propagatingTLS: provisioningNext check: 1ctl domains check api.example.comTracked in 1ctl#48.
Alias and www redirect workflows should be exposed
Section titled “Alias and www redirect workflows should be exposed”Planned, not implemented yet. Backend alias support already exists, but the current CLI cannot show one app serving multiple hostnames or expose the optional www redirect workflow.
Target shape
1ctl domains aliases list --app my-api1ctl domains aliases add api.example.com --app my-api1ctl domains aliases remove api.example.com --app my-api1ctl domains add example.com --app my-api --with-www-redirectTracked in 1ctl#49.
Recommended implementation order
Section titled “Recommended implementation order”-
Fix correctness before expanding UX
- explicit custom-domain updates must not be discarded (backend#340)
- route/domain drift must be reconciled (backend#337)
- partial-success responses must become truthful (backend#341)
-
Choose the canonical model
- unify the legacy ingress path and newer alias path (backend#342)
- migrate
1ctl domainsto that canonical API (1ctl#50)
-
Finish the Gateway API migration
- move custom domains from legacy
IngresstoHTTPRoute(backend#343) - formally deprecate and remove the old Ingress path after migration (backend#344)
- move custom domains from legacy
-
Make diagnostics first-class
- enrich the backend status API (backend#338)
- make
domains checkgenuinely diagnostic (1ctl#43) - add exact setup guidance (1ctl#48)
-
Expose richer product behavior
- alias and
wwwredirect workflows (1ctl#49) - rewrite the older custom-domain guide around the current product model (satu-docs#2)
- alias and
Manual verification while gaps remain
Section titled “Manual verification while gaps remain”Until reconciliation and diagnostics are fully implemented, verify all four planes when debugging a suspicious domain:
1ctl domains list1ctl domains check <domain>kubectl -n <namespace> get httproute,ingress -o widedig +short <domain> Acurl -k https://<domain>/healthThat manual sequence is exactly what the finished domains check command should eventually collapse into one truthful answer.