Skip to content

Commit 9150c20

Browse files
authored
Merge pull request #85 from Dstack-TEE/feat/ingress-wildcard-domain
feat(ingress): support wildcard custom domains
2 parents a690aa3 + 85af4dd commit 9150c20

4 files changed

Lines changed: 69 additions & 8 deletions

File tree

custom-domain/dstack-ingress/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,39 @@ The dstack-ingress now supports multiple domains in a single container:
5353
- Each domain gets its own SSL certificate
5454
- Flexible nginx configuration per domain
5555

56+
### Wildcard Domain Support
57+
58+
You can use a wildcard domain (e.g. `*.myapp.com`) to route all subdomains to a single dstack application:
59+
60+
- The TXT record is automatically set as `_dstack-app-address-wildcard.myapp.com` (instead of `_dstack-app-address.*.myapp.com`)
61+
- CAA records use the `issuewild` tag on the base domain
62+
- Requires dstack-gateway with wildcard TXT resolution support ([dstack#545](https://github.com/Dstack-TEE/dstack/pull/545))
63+
64+
```yaml
65+
services:
66+
dstack-ingress:
67+
image: dstacktee/dstack-ingress:latest
68+
ports:
69+
- "443:443"
70+
environment:
71+
- CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
72+
- DOMAIN=*.myapp.com
73+
- GATEWAY_DOMAIN=_.dstack-prod5.phala.network
74+
- CERTBOT_EMAIL=${CERTBOT_EMAIL}
75+
- SET_CAA=true
76+
- TARGET_ENDPOINT=http://app:80
77+
volumes:
78+
- /var/run/dstack.sock:/var/run/dstack.sock
79+
- /var/run/tappd.sock:/var/run/tappd.sock
80+
- cert-data:/etc/letsencrypt
81+
restart: unless-stopped
82+
app:
83+
image: nginx
84+
restart: unless-stopped
85+
volumes:
86+
cert-data:
87+
```
88+
5689
## Usage
5790
5891
### Prerequisites

custom-domain/dstack-ingress/scripts/entrypoint.sh

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ EOF
106106
setup_py_env
107107

108108
setup_nginx_conf() {
109+
local cert_name
110+
cert_name=$(cert_dir_name "$DOMAIN")
111+
109112
local client_max_body_size_conf=""
110113
if [ -n "$CLIENT_MAX_BODY_SIZE" ]; then
111114
client_max_body_size_conf=" client_max_body_size ${CLIENT_MAX_BODY_SIZE};"
@@ -148,8 +151,8 @@ server {
148151
server_name ${DOMAIN};
149152
150153
# SSL certificate configuration
151-
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
152-
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
154+
ssl_certificate /etc/letsencrypt/live/${cert_name}/fullchain.pem;
155+
ssl_certificate_key /etc/letsencrypt/live/${cert_name}/privkey.pem;
153156
154157
# Modern SSL configuration - TLS 1.2 and 1.3 only
155158
ssl_protocols TLSv1.2 TLSv1.3;
@@ -166,7 +169,7 @@ server {
166169
# Enable OCSP stapling
167170
ssl_stapling on;
168171
ssl_stapling_verify on;
169-
ssl_trusted_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
172+
ssl_trusted_certificate /etc/letsencrypt/live/${cert_name}/fullchain.pem;
170173
resolver 8.8.8.8 8.8.4.4 valid=300s;
171174
resolver_timeout 5s;
172175
@@ -231,8 +234,16 @@ set_txt_record() {
231234
fi
232235
APP_ID=${APP_ID:-"$DSTACK_APP_ID"}
233236

237+
local txt_domain
238+
if [[ "$domain" == \*.* ]]; then
239+
# Wildcard domain: *.myapp.com → _dstack-app-address-wildcard.myapp.com
240+
txt_domain="${TXT_PREFIX}-wildcard.${domain#\*.}"
241+
else
242+
txt_domain="${TXT_PREFIX}.${domain}"
243+
fi
244+
234245
dnsman.py set_txt \
235-
--domain "${TXT_PREFIX}.${domain}" \
246+
--domain "$txt_domain" \
236247
--content "$APP_ID:$PORT"
237248

238249
if [ $? -ne 0 ]; then
@@ -257,11 +268,20 @@ set_caa_record() {
257268
return
258269
fi
259270

271+
local caa_domain caa_tag
272+
if [[ "$domain" == \*.* ]]; then
273+
caa_domain="${domain#\*.}"
274+
caa_tag="issuewild"
275+
else
276+
caa_domain="$domain"
277+
caa_tag="issue"
278+
fi
279+
260280
ACCOUNT_URI=$(jq -j '.uri' "$account_file")
261-
echo "Adding CAA record for $domain, accounturi=$ACCOUNT_URI"
281+
echo "Adding CAA record ($caa_tag) for $caa_domain, accounturi=$ACCOUNT_URI"
262282
dnsman.py set_caa \
263-
--domain "$domain" \
264-
--caa-tag "issue" \
283+
--domain "$caa_domain" \
284+
--caa-tag "$caa_tag" \
265285
--caa-value "letsencrypt.org;validationmethods=dns-01;accounturi=$ACCOUNT_URI"
266286

267287
if [ $? -ne 0 ]; then

custom-domain/dstack-ingress/scripts/functions.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ sanitize_proxy_buffers() {
112112
fi
113113
}
114114

115+
# Get the certbot certificate directory name for a domain.
116+
# Certbot stores wildcard certs without the "*." prefix:
117+
# *.example.com → /etc/letsencrypt/live/example.com/
118+
cert_dir_name() {
119+
local domain="$1"
120+
echo "${domain#\*.}"
121+
}
122+
115123
get_letsencrypt_account_path() {
116124
local base_path="/etc/letsencrypt/accounts"
117125
local api_endpoint="acme-v02.api.letsencrypt.org"

custom-domain/dstack-ingress/scripts/generate-evidences.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ fi
2323
# Copy all certificate files
2424
while IFS= read -r domain; do
2525
[[ -n "$domain" ]] || continue
26-
cert_file="/etc/letsencrypt/live/${domain}/fullchain.pem"
26+
cert_file="/etc/letsencrypt/live/$(cert_dir_name "$domain")/fullchain.pem"
2727
if [ -f "$cert_file" ]; then
2828
cp "$cert_file" "cert-${domain}.pem"
2929
else

0 commit comments

Comments
 (0)