Skip to content

Commit 1a43a9c

Browse files
authored
Merge pull request #51 from Dstack-TEE/multi-domains
ingress: Support for multple domains
2 parents 4421535 + d0c301c commit 1a43a9c

File tree

14 files changed

+524
-215
lines changed

14 files changed

+524
-215
lines changed

custom-domain/dstack-ingress/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
/CLAUDE.md
33
/test/
44
__pycache__
5+
/oci.tar

custom-domain/dstack-ingress/Dockerfile

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ RUN set -e; \
88
echo 'deb [check-valid-until=no] https://snapshot.debian.org/archive/debian-security/20250411T024939Z bookworm-security main' >> /etc/apt/sources.list && \
99
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/10no-check-valid-until && \
1010
# Create preferences file to pin all packages
11+
rm -rf /etc/apt/sources.list.d/debian.sources && \
1112
mkdir -p /etc/apt/preferences.d && \
1213
cat /tmp/pinned-packages.txt | while read line; do \
1314
pkg=$(echo $line | cut -d= -f1); \
@@ -29,18 +30,12 @@ RUN set -e; \
2930
coreutils && \
3031
rm -rf /var/lib/apt/lists/* /var/log/* /var/cache/ldconfig/aux-cache /tmp/pinned-packages.txt
3132

32-
RUN mkdir -p /etc/letsencrypt /var/www/certbot /usr/share/nginx/html
33-
34-
# Set up Python virtual environment and install certbot
35-
RUN set -e; \
36-
python3 -m venv --system-site-packages /opt/app-venv && \
37-
. /opt/app-venv/bin/activate && \
38-
pip install --upgrade pip && \
39-
pip install certbot requests && \
40-
# Create symlinks for system-wide access
41-
ln -sf /opt/app-venv/bin/certbot /usr/local/bin/certbot && \
42-
# Ensure the virtual environment is always activated for scripts
43-
echo 'source /opt/app-venv/bin/activate' > /etc/profile.d/app-venv.sh
33+
RUN mkdir -p \
34+
/etc/letsencrypt \
35+
/var/www/certbot \
36+
/usr/share/nginx/html \
37+
/etc/nginx/conf.d \
38+
/var/log/nginx
4439

4540
COPY ./scripts /scripts/
4641
RUN chmod +x /scripts/*.sh /scripts/*.py
@@ -50,4 +45,3 @@ COPY .GIT_REV /etc/
5045

5146
ENTRYPOINT ["/scripts/entrypoint.sh"]
5247
CMD ["nginx", "-g", "daemon off;"]
53-

custom-domain/dstack-ingress/README.md

Lines changed: 93 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,26 @@ The dstack-ingress system provides a seamless way to set up custom domains for d
3333
3. **Certificate Management**:
3434

3535
- SSL certificates are automatically obtained during initial setup
36-
- A scheduled task runs twice daily to check for certificate renewal
36+
- A simple background daemon checks for certificate renewal every 12 hours
3737
- When certificates are renewed, Nginx is automatically reloaded to use the new certificates
38+
- Uses a simple sleep loop instead of cron for reliability and easier debugging in containers
3839

3940
4. **Evidence Generation**:
4041
- The system generates evidence files for verification purposes
4142
- These include the ACME account information and certificate data
4243
- Evidence files are accessible through a dedicated endpoint
4344

45+
## Features
46+
47+
### Multi-Domain Support (New!)
48+
49+
The dstack-ingress now supports multiple domains in a single container:
50+
51+
- **Single Domain Mode** (backward compatible): Use `DOMAIN` and `TARGET_ENDPOINT` environment variables
52+
- **Multi-Domain Mode**: Use `DOMAINS` environment variable with custom nginx configurations in `/etc/nginx/conf.d/`
53+
- Each domain gets its own SSL certificate
54+
- Flexible nginx configuration per domain
55+
4456
## Usage
4557

4658
### Prerequisites
@@ -50,7 +62,7 @@ The dstack-ingress system provides a seamless way to set up custom domains for d
5062

5163
### Deployment
5264

53-
You can either build the ingress container and push it to docker hub, or use the prebuilt image at `kvin/dstack-ingress`.
65+
You can either build the ingress container and push it to docker hub, or use the prebuilt image at `dstacktee/dstack-ingress:20250924`.
5466

5567
#### Option 1: Use the Pre-built Image
5668

@@ -59,7 +71,7 @@ The fastest way to get started is to use our pre-built image. Simply use the fol
5971
```yaml
6072
services:
6173
dstack-ingress:
62-
image: kvin/dstack-ingress@sha256:b61d50360c7a4e5ab7d22f5ce87677714f3f64a65db34ee5eebcc54683950c89
74+
image: dstacktee/dstack-ingress:20250924@sha256:40429d78060ef3066b5f93676bf3ba7c2e9ac47d4648440febfdda558aed4b32
6375
ports:
6476
- "443:443"
6577
environment:
@@ -87,15 +99,90 @@ volumes:
8799
cert-data: # Persistent volume for certificates
88100
```
89101
102+
### Multi-Domain Configuration
103+
104+
```yaml
105+
services:
106+
ingress:
107+
image: dstacktee/dstack-ingress:20250924@sha256:40429d78060ef3066b5f93676bf3ba7c2e9ac47d4648440febfdda558aed4b32
108+
ports:
109+
- "443:443"
110+
environment:
111+
DNS_PROVIDER: cloudflare
112+
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
113+
CERTBOT_EMAIL: ${CERTBOT_EMAIL}
114+
GATEWAY_DOMAIN: _.dstack-prod5.phala.network
115+
SET_CAA: true
116+
DOMAINS: |
117+
${APP_DOMAIN}
118+
${API_DOMAIN}
119+
120+
volumes:
121+
- /var/run/tappd.sock:/var/run/tappd.sock
122+
- letsencrypt:/etc/letsencrypt
123+
124+
configs:
125+
- source: app_conf
126+
target: /etc/nginx/conf.d/app.conf
127+
mode: 0444
128+
- source: api_conf
129+
target: /etc/nginx/conf.d/api.conf
130+
mode: 0444
131+
132+
restart: unless-stopped
133+
134+
app-main:
135+
image: nginx
136+
restart: unless-stopped
137+
138+
app-api:
139+
image: nginx
140+
restart: unless-stopped
141+
142+
volumes:
143+
letsencrypt:
144+
145+
configs:
146+
app_conf:
147+
content: |
148+
server {
149+
listen 443 ssl;
150+
server_name ${APP_DOMAIN};
151+
ssl_certificate /etc/letsencrypt/live/${APP_DOMAIN}/fullchain.pem;
152+
ssl_certificate_key /etc/letsencrypt/live/${APP_DOMAIN}/privkey.pem;
153+
location / {
154+
proxy_pass http://app-main:80;
155+
}
156+
}
157+
api_conf:
158+
content: |
159+
server {
160+
listen 443 ssl;
161+
server_name ${API_DOMAIN};
162+
ssl_certificate /etc/letsencrypt/live/${API_DOMAIN}/fullchain.pem;
163+
ssl_certificate_key /etc/letsencrypt/live/${API_DOMAIN}/privkey.pem;
164+
location / {
165+
proxy_pass http://app-api:80;
166+
}
167+
}
168+
```
169+
90170
**Core Environment Variables:**
91171
92172
- `DNS_PROVIDER`: DNS provider to use (cloudflare, linode)
93-
- `DOMAIN`: Your custom domain
173+
- `DOMAIN`: Your custom domain (for single domain mode)
174+
- `DOMAINS`: Multiple domains, one per line (supports environment variable substitution like `${APP_DOMAIN}`)
94175
- `GATEWAY_DOMAIN`: The dstack gateway domain (e.g. `_.dstack-prod5.phala.network` for Phala Cloud)
95176
- `CERTBOT_EMAIL`: Your email address used in Let's Encrypt certificate requests
96-
- `TARGET_ENDPOINT`: The plain HTTP endpoint of your dstack application
177+
- `TARGET_ENDPOINT`: The plain HTTP endpoint of your dstack application (for single domain mode)
97178
- `SET_CAA`: Set to `true` to enable CAA record setup
98179

180+
**Backward Compatibility:**
181+
182+
- If both `DOMAIN` and `TARGET_ENDPOINT` are set, the system operates in single-domain mode with auto-generated nginx config
183+
- If `DOMAINS` is set, the system operates in multi-domain mode and expects custom nginx configs in `/etc/nginx/conf.d/`
184+
- You can use both modes simultaneously
185+
99186
For provider-specific configuration details, see [DNS Provider Configuration](DNS_PROVIDERS.md).
100187

101188
#### Option 2: Build Your Own Image
@@ -126,7 +213,6 @@ docker push yourusername/dstack-ingress:tag
126213

127214
4. Update the docker-compose.yaml file with your image name and deploy
128215

129-
130216
#### gRPC Support
131217

132218
If your dstack application uses gRPC, you can set `TARGET_ENDPOINT` to `grpc://app:50051`.
@@ -136,7 +222,7 @@ example:
136222
```yaml
137223
services:
138224
dstack-ingress:
139-
image: kvin/dstack-ingress@sha256:b61d50360c7a4e5ab7d22f5ce87677714f3f64a65db34ee5eebcc54683950c89
225+
image: dstacktee/dstack-ingress:20250924@sha256:40429d78060ef3066b5f93676bf3ba7c2e9ac47d4648440febfdda558aed4b32
140226
ports:
141227
- "443:443"
142228
environment:
Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,71 @@
11
#!/bin/bash
2-
NAME=$1
3-
if [ -z "$NAME" ]; then
4-
echo "Usage: $0 <name>[:<tag>]"
5-
exit 1
6-
fi
2+
3+
# Parse command line arguments
4+
PUSH=false
5+
REPO=""
6+
7+
while [[ $# -gt 0 ]]; do
8+
case $1 in
9+
--push)
10+
PUSH=true
11+
REPO="$2"
12+
if [ -z "$REPO" ]; then
13+
echo "Error: --push requires a repository argument"
14+
echo "Usage: $0 [--push <repo>[:<tag>]]"
15+
exit 1
16+
fi
17+
shift 2
18+
;;
19+
*)
20+
echo "Usage: $0 [--push <repo>[:<tag>]]"
21+
exit 1
22+
;;
23+
esac
24+
done
725
# Check if buildkit_20 already exists before creating it
826
if ! docker buildx inspect buildkit_20 &>/dev/null; then
927
docker buildx create --use --driver-opt image=moby/buildkit:v0.20.2 --name buildkit_20
1028
fi
1129
touch pinned-packages.txt
1230
git rev-parse HEAD > .GIT_REV
13-
docker buildx build --builder buildkit_20 --no-cache --build-arg SOURCE_DATE_EPOCH="0" --output type=docker,name="$NAME",rewrite-timestamp=true .
14-
docker run --rm --entrypoint bash "$NAME" -c "dpkg -l | grep '^ii' |awk '{print \$2\"=\"\$3}' | sort" > pinned-packages.txt
31+
TEMP_TAG="dstack-ingress-temp:$(date +%s)"
32+
docker buildx build --builder buildkit_20 --no-cache --build-arg SOURCE_DATE_EPOCH="0" \
33+
--output type=oci,dest=./oci.tar,rewrite-timestamp=true \
34+
--output type=docker,name="$TEMP_TAG" .
35+
36+
if [ "$?" -ne 0 ]; then
37+
echo "Build failed"
38+
rm .GIT_REV
39+
exit 1
40+
fi
41+
42+
echo "Build completed, manifest digest:"
43+
echo ""
44+
skopeo inspect oci-archive:./oci.tar | jq .Digest
45+
echo ""
46+
47+
if [ "$PUSH" = true ]; then
48+
echo "Pushing image to $REPO..."
49+
skopeo copy --insecure-policy oci-archive:./oci.tar docker://"$REPO"
50+
echo "Image pushed successfully to $REPO"
51+
else
52+
echo "To push the image to a registry, run:"
53+
echo ""
54+
echo " $0 --push <repo>[:<tag>]"
55+
echo ""
56+
echo "Or use skopeo directly:"
57+
echo ""
58+
echo " skopeo copy --insecure-policy oci-archive:./oci.tar docker://<repo>[:<tag>]"
59+
fi
60+
echo ""
61+
62+
# Extract package information from the built image
63+
echo "Extracting package information from built image: $TEMP_TAG"
64+
docker run --rm --entrypoint bash "$TEMP_TAG" -c "dpkg -l | grep '^ii' | awk '{print \$2\"=\"\$3}' | sort" > pinned-packages.txt
65+
66+
echo "Package information extracted to pinned-packages.txt ($(wc -l < pinned-packages.txt) packages)"
67+
68+
# Clean up the temporary image from Docker daemon
69+
docker rmi "$TEMP_TAG" 2>/dev/null || true
70+
1571
rm .GIT_REV
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
services:
2+
ingress:
3+
image: dstacktee/dstack-ingress:20250924@sha256:40429d78060ef3066b5f93676bf3ba7c2e9ac47d4648440febfdda558aed4b32
4+
ports:
5+
- "443:443"
6+
environment:
7+
DNS_PROVIDER: cloudflare
8+
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
9+
CERTBOT_EMAIL: ${CERTBOT_EMAIL}
10+
GATEWAY_DOMAIN: _.dstack-prod5.phala.network
11+
SET_CAA: true
12+
DOMAINS: |
13+
${APP_DOMAIN}
14+
${API_DOMAIN}
15+
16+
volumes:
17+
- /var/run/tappd.sock:/var/run/tappd.sock
18+
- letsencrypt:/etc/letsencrypt
19+
20+
configs:
21+
- source: app_conf
22+
target: /etc/nginx/conf.d/app.conf
23+
mode: 0444
24+
- source: api_conf
25+
target: /etc/nginx/conf.d/api.conf
26+
mode: 0444
27+
28+
restart: unless-stopped
29+
30+
app-main:
31+
image: nginx
32+
restart: unless-stopped
33+
34+
app-api:
35+
image: nginx
36+
restart: unless-stopped
37+
38+
volumes:
39+
letsencrypt:
40+
41+
configs:
42+
app_conf:
43+
content: |
44+
server {
45+
listen 443 ssl;
46+
server_name ${APP_DOMAIN};
47+
48+
ssl_certificate /etc/letsencrypt/live/${APP_DOMAIN}/fullchain.pem;
49+
ssl_certificate_key /etc/letsencrypt/live/${APP_DOMAIN}/privkey.pem;
50+
51+
location / {
52+
proxy_pass http://app-main:80;
53+
}
54+
}
55+
api_conf:
56+
content: |
57+
server {
58+
listen 443 ssl;
59+
server_name ${API_DOMAIN};
60+
61+
ssl_certificate /etc/letsencrypt/live/${API_DOMAIN}/fullchain.pem;
62+
ssl_certificate_key /etc/letsencrypt/live/${API_DOMAIN}/privkey.pem;
63+
64+
location / {
65+
proxy_pass http://app-api:80;
66+
}
67+
}

custom-domain/dstack-ingress/docker-compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
services:
22
dstack-ingress:
3-
image: kvin/dstack-ingress@sha256:b61d50360c7a4e5ab7d22f5ce87677714f3f64a65db34ee5eebcc54683950c89
3+
image: dstacktee/dstack-ingress:20250924@sha256:40429d78060ef3066b5f93676bf3ba7c2e9ac47d4648440febfdda558aed4b32
44
ports:
55
- "443:443"
66
environment:

0 commit comments

Comments
 (0)