Portal Domain¶
SeaTicket can expose a project's support portal through either of these domain types:
- A project subdomain managed by the SeaTicket administrator, such as
example.portal-seaticket.com. - A custom domain managed by the project administrator, such as
support.yourdomain.com.
This guide uses the following example values:
| Item | Example |
|---|---|
| SeaTicket domain | cloud.seaticket.ai |
| Portal service root domain | portal-seaticket.com |
| Project portal domain | example.portal-seaticket.com |
| Project custom domain | support.yourdomain.com |
| SeaTicket management upstream | http://seaqa-web:80 |
| SeaTicket portal upstream | http://seaqa-web-portal:80 |
Important
PORTAL_SERVICE_ROOT_DOMAIN configures domain matching inside SeaTicket. It does not create DNS records, start the Portal-mode service, create reverse proxy routes, or issue TLS certificates. Complete all of the application, DNS, service, ingress, and TLS steps below.
Deployment architecture¶
Portal domains must be served by a separate seaqa-web instance running in Portal mode:
| Public host | Application mode | Upstream example |
|---|---|---|
cloud.seaticket.ai |
Main mode (default) | seaqa-web:80 |
*.portal-seaticket.com |
Portal mode | seaqa-web-portal:80 |
| Verified custom domains | Portal mode | seaqa-web-portal:80 |
Portal mode is enabled with the following environment variable:
SEAQA_APP_MODE=portal
The Portal-mode instance uses the same SeaTicket image version, database, Redis, SeaDB, S3 storage, configuration file, and shared data as the main instance. Give the Portal instance a different Compose service and container name, and do not replace the main instance.
Download the Portal-mode Web and Caddy extension Compose files into /opt/seaticket:
cd /opt/seaticket
wget https://manual.seaticket.ai/0.9/repo/docker/seaticket/seaqa-web-portal.yml
wget https://manual.seaticket.ai/0.9/repo/docker/seaticket/caddy-portal.yml
Add the files to COMPOSE_FILE in /opt/seaticket/.env and configure the Portal domain values:
COMPOSE_FILE='caddy.yml,caddy-portal.yml,seaqa-web.yml,seaqa-web-portal.yml'
COMPOSE_PATH_SEPARATOR=','
PORTAL_SERVICE_ROOT_DOMAIN=portal-seaticket.com
PORTAL_CERTS_VOLUME=/opt/caddy-certs
seaqa-web-portal.yml copies the backend, storage, secret, volume, and network settings used by the main Web service and adds SEAQA_APP_MODE=portal.
caddy-portal.yml does not start a second Caddy container. Docker Compose merges files by service name, so its caddy section extends the existing caddy service from caddy.yml. The base file still owns the Caddy image, ports, Docker socket, persistent data, and network; the Portal extension mounts PORTAL_CERTS_VOLUME at /certs so the wildcard certificate is available inside the existing Caddy container.
Keeping this mount in a separate Compose file makes Portal support optional. Deployments that do not use Portal domains can continue using only caddy.yml without creating a wildcard certificate directory.
Configure the portal service root domain¶
Add the following setting to /opt/seaticket/seaticket-data/conf/seaticket_config.yaml:
seaqa-web:
PORTAL_SERVICE_ROOT_DOMAIN: portal-seaticket.com
Install the wildcard certificate¶
Place one certificate and private key valid for *.portal-seaticket.com in the directory configured by PORTAL_CERTS_VOLUME:
mkdir -p /opt/caddy-certs
cp <wildcard-fullchain-file> /opt/caddy-certs/portal-fullchain.pem
cp <wildcard-private-key-file> /opt/caddy-certs/portal-privkey.pem
chmod 600 /opt/caddy-certs/portal-privkey.pem
This one wildcard certificate is shared by all Portal subdomains.
Validate the merged Compose configuration, then restart the services
After Caddy loads the *.portal-seaticket.com route, new project Portal subdomains reuse the installed wildcard certificate automatically. The deployment administrator only needs to replace this one certificate before it expires and restart Caddy to load the renewed files.
Configure DNS for project portal domains¶
Create a wildcard DNS record that points all project subdomains to the public ingress serving SeaTicket.
When the ingress has a public IP address:
| Type | Name | Value |
|---|---|---|
A |
*.portal-seaticket.com |
<ingress-public-ipv4> |
When the ingress already has a DNS name:
| Type | Name | Value |
|---|---|---|
CNAME |
*.portal-seaticket.com |
<ingress-hostname> |
Do not create both a CNAME and an A record for the same wildcard name.
Verify that the wildcard record resolves before continuing:
dig +short example.portal-seaticket.com
Configure the ingress and TLS¶
The public ingress must:
- Accept
*.portal-seaticket.comin addition tocloud.seaticket.ai. - Forward the SeaTicket management domain to the main-mode instance.
- Forward Portal subdomains and custom domains to the Portal-mode instance.
- Preserve the original
Hostheader so SeaTicket can identify the project portal. - Set
X-Forwarded-Prototo the client-facing protocol. - Serve the configured certificate valid for
*.portal-seaticket.com.
A single wildcard certificate covers all first-level project subdomains under portal-seaticket.com. Once the wildcard DNS record and certificate are active, adding a project Portal does not require another DNS record or certificate.
The equivalent Caddy behavior is shown below. Adapt the syntax to the ingress used by the deployment:
https://*.portal-seaticket.com {
tls /certs/portal-fullchain.pem /certs/portal-privkey.pem
reverse_proxy seaqa-web-portal:80
}
Caddy preserves the original Host header and sets the standard forwarded headers by default. Other ingress implementations must preserve the original Host header and set X-Forwarded-Proto to the client-facing protocol explicitly when they do not do so automatically.
Docker deployment with Caddy
The base Docker Compose files route only SEATICKET_SERVER_HOSTNAME to the main-mode instance. The additional seaqa-web-portal.yml labels route *.portal-seaticket.com and verified custom domains to seaqa-web-portal. The caddy_0.tls label loads the shared wildcard certificate. If TLS is terminated by an external load balancer instead, configure the wildcard certificate and the same routing there, and preserve the original Host header through every proxy layer.
Configure TLS for custom domains¶
Unlike the Portal service domain, customer-owned domains cannot be covered by one administrator-managed wildcard certificate. The Portal-mode service provides an authorization endpoint for Caddy on-demand TLS. Caddy should call it over the private application network:
http://seaqa-web-portal/internal/portal/custom-domain/allow-tls?domain=<requested-domain>
It returns success only when the custom domain is verified and its project Portal is enabled. The labels in seaqa-web-portal.yml generate the equivalent of this Caddy behavior:
{
on_demand_tls {
ask http://seaqa-web-portal/internal/portal/custom-domain/allow-tls
}
}
https:// {
tls {
on_demand
}
reverse_proxy seaqa-web-portal:80
}
The caddy_0 labels create the explicit Portal route using the shared *.portal-seaticket.com certificate. The caddy_1 labels create the catch-all HTTPS route with on-demand TLS. The global caddy.on_demand_tls.ask label restricts certificate issuance to custom domains approved by SeaTicket.
Expected certificate behavior¶
After DNS, Caddy, and SeaTicket are configured:
| Domain type | Certificate behavior |
|---|---|
Project Portal subdomains such as example.portal-seaticket.com |
They reuse the configured *.portal-seaticket.com certificate. Creating another project subdomain does not trigger or require separate certificate issuance. |
Verified custom domains such as support.yourdomain.com |
They are not covered by the Portal wildcard certificate. On the first TLS connection, Caddy asks SeaTicket whether the domain is authorized and automatically obtains an individual certificate only when the custom domain is verified and its Portal is enabled. |
The shared wildcard certificate is managed by the deployment administrator, while Caddy automatically obtains and renews certificates for approved custom domains. A Portal subdomain is usable after the wildcard DNS record resolves and the shared certificate is installed. A custom domain is usable after its DNS points to the ingress and SeaTicket's authorization endpoint approves it.
Inspect the Caddyfile generated from the Docker labels after deployment:
docker exec caddy cat /config/caddy/Caddyfile.autosave
Confirm that it contains the *.portal-seaticket.com site with the wildcard certificate paths, the on_demand_tls global option, the SeaTicket ask URL, and the catch-all HTTPS site.
Warning
Never enable unrestricted on-demand TLS. Always configure the SeaTicket ask endpoint; otherwise arbitrary hostnames can trigger certificate issuance and exhaust CA rate limits.
Enable and test a project portal¶
- Sign in to SeaTicket as a project administrator.
- Open the project settings and enable Support portal.
- Open the portal settings and select Portal domain.
- Set a unique prefix, such as
example, and save it. - Open
https://example.portal-seaticket.com.
The prefix must contain 3 to 63 letters, numbers, or hyphens, and cannot begin or end with a hyphen. Reserved prefixes such as admin, api, auth, static, support, and www cannot be used.
You can verify routing from a machine that can reach the ingress:
curl -I https://example.portal-seaticket.com
To test the route before public DNS is available, resolve the domain to the ingress IP only for the curl request:
curl --resolve example.portal-seaticket.com:443:<ingress-public-ipv4> \
-I https://example.portal-seaticket.com
Configure a project custom domain¶
Custom domains are configured by a project administrator after the portal service root domain is working.
- Open the project's portal settings and select Custom domain.
- Enter the fully qualified domain without a scheme or path, for example
support.yourdomain.com, and save it. -
Add the CNAME record displayed by SeaTicket. It points the custom domain to the project's current portal domain:
Type Name Value CNAMEsupport.yourdomain.comexample.portal-seaticket.com -
Add the TXT record name and value displayed by SeaTicket. The values are unique to the project and must be copied exactly.
- Ensure the ingress has already been configured for custom-domain routing and certificate automation.
- Wait for public DNS propagation, then select Verify in SeaTicket.
- Open
https://support.yourdomain.comand confirm that it loads the expected portal.
Prefer a subdomain for the custom domain. A DNS zone apex cannot normally use a CNAME record; it requires provider-specific ALIAS, ANAME, or CNAME-flattening support that targets the displayed Portal domain. The custom domain cannot be the SeaTicket management domain, the Portal root domain, or a subdomain below the Portal root domain.
Deployment checklist¶
PORTAL_SERVICE_ROOT_DOMAINis set inseaticket_config.yaml.- A separate
seaqa-webinstance is running withSEAQA_APP_MODE=portal. - Both Web instances use the same SeaTicket configuration and backend services.
- Both Web instances were restarted after the configuration change.
*.portal-seaticket.comresolves to the public ingress.- The ingress routes the management domain to main mode and Portal domains to Portal mode.
- The ingress preserves the original
Hostheader. - The TLS certificate covers
*.portal-seaticket.com. - The main-mode
seaqa-webcontainer can query8.8.8.8and1.1.1.1over UDP and TCP port 53. - A project portal opens successfully through its generated subdomain.
- Each custom domain has its CNAME, TXT verification record, ingress route, and TLS certificate.