-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathcompose.override.sdlc.yaml
More file actions
611 lines (532 loc) · 21 KB
/
Copy pathcompose.override.sdlc.yaml
File metadata and controls
611 lines (532 loc) · 21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
#########################################################################
# This Compose file extends the default Labspace setup by adding the following elements:
#
# - A Traefik reverse proxy to route traffic to Gitea and k3s
# - A Gitea server for Git hosting and CI/CD
# - A Gitea self-hosted runner for executing CI jobs
# - A k3s Kubernetes cluster
#
# This provides a "SDLC-in-a-box" environment for end-to-end labs.
#
# Additional documentation is found in the docs/sdlc-labspace.md file.
#########################################################################
services:
############################################################
# Overrides to Labspace default services
############################################################
workspace:
environment:
SKIP_CI_SECRET_SETUP: ${SKIP_CI_SECRET_SETUP:-false}
volumes:
- gitea-outputs:/gitea-outputs:ro
- k3s-output:/kubeconfig:ro
- type: volume
source: labspace-traefik-certs
target: /traefik-ca
volume:
subpath: ca/
- type: volume
source: labspace-support
target: /etc/labspace-support/k8s-setup
volume:
subpath: k8s-setup
configs:
- source: workspace-entrypoint-script
target: /entrypoint.d/50-entrypoint-script.sh
mode: 0755
- source: workspace-wait-for-bootstrap
target: /startup-scripts/00-wait-for-bootstrap.sh
mode: 0755
- source: workspace-ca-cert-setup
target: /startup-scripts/05-ca-cert-setup.sh
mode: 0755
- source: workspace-ci-secrets
target: /startup-scripts/10-ci-secrets.sh
mode: 0755
- source: workspace-kubeconfig-setup
target: /startup-scripts/10-kubeconfig-setup.sh
mode: 0755
- source: workspace-git-setup
target: /startup-scripts/20-git-setup.sh
mode: 0755
- source: workspace-configure-k3s-coredns
target: /startup-scripts/15-configure-k3s-coredns.sh
mode: 0755
depends_on:
cert-creator:
condition: service_completed_successfully
git:
condition: service_healthy
k3s:
condition: service_healthy
zone-file-creator:
condition: service_completed_successfully
interface:
labels:
- "traefik.enable=true"
- "traefik.http.routers.interface.rule=Host(`dockerlabs.xyz`)"
- "traefik.http.routers.interface.entrypoints=web"
- "traefik.http.routers.interface.service=interface"
- "traefik.http.services.interface.loadbalancer.server.port=3030"
############################################################
# New services
############################################################
cert-creator:
image: alpine
command: /generate-certs.sh
configs:
- source: generate-traefik-certs
target: /generate-certs.sh
mode: 0755
volumes:
- labspace-traefik-certs:/certs
zone-file-creator:
image: dhi.io/wbitt-network-multitool:3-alpine3.23-dev
command: /create-zone-file.sh
configs:
- source: create-zone-file
target: /create-zone-file.sh
mode: 0755
volumes:
- labspace-support:/labspace-support
depends_on:
traefik:
condition: service_started
traefik:
image: traefik:3.6.13
command:
- --api.insecure=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.ssh.address=:22
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls=true
- --providers.file.filename=/etc/traefik/cert-config.yaml
ports:
- "22:22"
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- labspace-traefik-certs:/certs:ro
configs:
- source: traefik-cert-config
target: /etc/traefik/cert-config.yaml
networks:
default:
aliases:
- git.dockerlabs.xyz
- registry.dockerlabs.xyz
- app.dockerlabs.xyz
depends_on:
cert-creator:
condition: service_completed_successfully
registry:
image: ghcr.io/project-zot/zot:v2.1.15
labels:
traefik.enable: true
traefik.http.routers.registry.rule: Host(`registry.dockerlabs.xyz`)
traefik.http.services.registry.loadbalancer.server.port: "5000"
traefik.http.middlewares.remove-frame-headers.headers.customresponseheaders.X-Frame-Options: ""
traefik.http.routers.registry.middlewares: remove-frame-headers
git:
image: gitea/gitea:1.25.5
volumes:
- gitea-data:/data
- gitea-outputs:/gitea-outputs
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
configs:
- source: gitea-bootstrap
target: /etc/gitea/bootstrap.sh
mode: 0755
environment:
GITEA__server__ROOT_URL: http://git.dockerlabs.xyz/
GITEA__server__SSH_DOMAIN: git.dockerlabs.xyz
GITEA__server__SSH_LISTEN_PORT: 22
GITEA__database__DB_TYPE: sqlite3
# This prevents the first-time install page from showing (setup is done via script/env vars)
GITEA__security__INSTALL_LOCK: "true"
labels:
traefik.enable: "true"
traefik.http.routers.git.rule: Host(`git.dockerlabs.xyz`)
traefik.http.routers.git.entrypoints: web
traefik.http.routers.git.service: git
traefik.http.services.git.loadbalancer.server.port: "3000"
traefik.http.middlewares.remove-frame-headers.headers.customresponseheaders.X-Frame-Options: ""
traefik.http.routers.git.middlewares: remove-frame-headers
traefik.http.routers.git-secure.rule: Host(`git.dockerlabs.xyz`)
traefik.http.routers.git-secure.entrypoints: websecure
traefik.http.routers.git-secure.service: git
traefik.tcp.routers.git-ssh.rule: HostSNI(`*`)
traefik.tcp.routers.git-ssh.entrypoints: ssh
traefik.tcp.routers.git-ssh.service: git-ssh
traefik.tcp.services.git-ssh.loadbalancer.server.port: "22"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"]
interval: 10s
timeout: 10s
retries: 3
start_period: 5s
post_start:
- command: bash /etc/gitea/bootstrap.sh
user: root
gitea-runner:
image: gitea/act_runner:0.3.1
entrypoint: []
command: bash -c "GITEA_RUNNER_REGISTRATION_TOKEN=$(cat /gitea-outputs/runner_token.txt) /sbin/tini -- run.sh"
volumes:
- type: volume
source: socket-proxy
target: /var/run/docker.sock
volume:
subpath: docker.sock
- gitea-outputs:/gitea-outputs
environment:
GITEA_INSTANCE_URL: http://git.dockerlabs.xyz
GITEA_RUNNER_NAME: labspace-runner
# The runner uses 1.41 Docker API by default, but we need 1.45+ when using the socket proxy
# due to volume rewrites using volume subpaths
DOCKER_API_VERSION: "1.45"
depends_on:
git:
condition: service_healthy
k3s:
# When updating the k8s version, ensure the kubectl version in the base workspace image is updated
image: rancher/k3s:v1.35.3-k3s1
command: server --tls-san k8s.dockerlabs.xyz
privileged: true
volumes:
- k3s-server:/var/lib/rancher/k3s
- k3s-output:/output
environment:
- K3S_TOKEN=labspace-k3s-token
- K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
- K3S_KUBECONFIG_MODE=666
tmpfs:
- /run
- /var/run
ulimits:
nproc: 65535
nofile:
soft: 65535
hard: 65535
networks:
default:
aliases:
- k8s.dockerlabs.xyz
configs:
- source: k3s-registry-config
target: /etc/rancher/k3s/registries.yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.prod-app.rule=Host(`app.dockerlabs.xyz`)"
- "traefik.http.routers.prod-app.entrypoints=web"
- "traefik.http.routers.prod-app.service=prod-app"
- "traefik.http.services.prod-app.loadbalancer.server.port=80"
healthcheck:
test: ["CMD", "kubectl", "get", "--raw", "/readyz"]
interval: 10s
timeout: 10s
retries: 10
start_period: 20s
volumes:
gitea-data:
gitea-outputs:
k3s-server:
k3s-output:
labspace-traefik-certs:
configs:
# Generate the certs for Traefik to use
generate-traefik-certs:
content: |
#!/bin/sh
apk add --no-cache openssl >/dev/null 2>&1 || true;
mkdir -p /certs/ca
if [ -f /certs/dockerlabs.crt ] && [ -f /certs/dockerlabs.key ]; then
echo 'Certs already exist, skipping generation';
return
fi
# Create CA (one-time)
if [ ! -f /certs/ca/ca.crt ] || [ ! -f /certs/ca/ca.key ]; then
openssl genrsa -out /certs/ca/ca.key 4096
openssl req -x509 -new -nodes -key /certs/ca/ca.key -sha256 -days 3650 \
-subj "/CN=dockerlabs-local-ca" \
-out /certs/ca/ca.crt
fi
# Create leaf key + CSR
openssl genrsa -out /certs/dockerlabs.key 4096
cat > /tmp/leaf.cnf <<'CNF'
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = dockerlabs.xyz
[v3_req]
subjectAltName = @alt_names
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[alt_names]
DNS.1 = dockerlabs.xyz
DNS.2 = *.dockerlabs.xyz
CNF
openssl req -new -key /certs/dockerlabs.key -out /tmp/dockerlabs.csr -config /tmp/leaf.cnf
# Sign leaf cert with CA
openssl x509 -req -in /tmp/dockerlabs.csr \
-CA /certs/ca/ca.crt -CAkey /certs/ca/ca.key -CAcreateserial \
-out /certs/dockerlabs.crt -days 825 -sha256 \
-extfile /tmp/leaf.cnf -extensions v3_req
# combine cert & key into a PEM (optional)
cat /certs/dockerlabs.crt /certs/dockerlabs.key > /certs/dockerlabs.pem || true;
echo 'Generated CA and certificates';
find /certs -type f
# Traefik configuration to use the generated certs
traefik-cert-config:
content: |
tls:
certificates:
- certFile: /certs/dockerlabs.crt
keyFile: /certs/dockerlabs.key
stores:
default:
defaultCertificate:
certFile: /certs/dockerlabs.crt
keyFile: /certs/dockerlabs.key
# Override the default allow list to add buildkit builders and the gitea volumes
docker-proxy-mount-allowlist:
content: |
gates:
- type: mountSource
allowedSources:
- labspace-content
- labspace-socket-proxy
- buildx_buildkit_default_state
- buildx_buildkit_builder-*
- label:labspace-resource=true
- act-toolcache
- GITEA*
# The /entrypoint.d hook in code-server does not guarantee order of execution, as it simply uses `find` to get files in the
# directory. To ensure our scripts run in the correct order, we prefix them with numbers and use a custom entrypoint script
# to sort and execute the scripts in order.
workspace-entrypoint-script:
content: |
#!/bin/sh
set -eu
CUSTOM_DIR="/startup-scripts"
# Nothing to do if the directory doesn't exist
[ -d "$$CUSTOM_DIR" ] || exit 0
# Find executable regular files, sort them, and run one by one
find "$$CUSTOM_DIR" -type f -executable -print \
| sort \
| while IFS= read -r script; do
echo "→ Running $$script"
"$$script"
done
# Waits for the Gitea bootstrap to complete before allowing other startup scripts to proceed.
# Validates that the moby user exists and the SSH key is registered, which are the last steps
# of the bootstrap and gate everything the workspace startup scripts depend on.
workspace-wait-for-bootstrap:
content: |
#!/bin/bash
echo "Waiting for Gitea bootstrap to complete..."
until curl -sf -u 'moby:moby1234' http://git.dockerlabs.xyz/api/v1/user > /dev/null 2>&1; do
echo "Waiting for moby user to be ready..."
sleep 2
done
echo "moby user is ready."
until curl -s -u 'moby:moby1234' http://git.dockerlabs.xyz/api/v1/user/keys | grep -q '"moby-key"'; do
echo "Waiting for SSH key to be registered..."
sleep 2
done
echo "SSH key is registered. Bootstrap complete."
# Sets up the CA cert used by Traefik (the reverse proxy for all of the lab) in the workspace, so that the
# workspace can trust the TLS connections to Gitea and k3s using the Traefik certs
workspace-ca-cert-setup:
content: |
#!/bin/bash
set -e
sudo cp /traefik-ca/ca.crt /usr/local/share/ca-certificates/traefik-ca.crt
sudo update-ca-certificates
# Creates all CI secrets in the Gitea repo. Can be skipped by setting SKIP_CI_SECRET_SETUP=true,
# which is useful for labs that guide users through creating the secrets themselves.
workspace-ci-secrets:
content: |
#!/bin/bash
if [ "$$SKIP_CI_SECRET_SETUP" = "true" ]; then
echo "SKIP_CI_SECRET_SETUP is set. Skipping CI secret creation."
exit 0
fi
SECRETS_JSON=$(curl -s -u 'moby:moby1234' http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets)
# Create Gitea registry secrets
if echo "$$SECRETS_JSON" | grep -q '"DOCKER_USERNAME"'; then
echo "Gitea registry secrets already exist. Skipping."
else
curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKER_USERNAME -H "Content-Type: application/json" -d '{"data":"moby"}'
curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKER_PASSWORD -H "Content-Type: application/json" -d '{"data":"moby1234"}'
curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKER_REGISTRY -H "Content-Type: application/json" -d '{"data":"registry.dockerlabs.xyz"}'
echo "Gitea registry secrets created."
fi
# Create Docker Hub secrets
if echo "$$SECRETS_JSON" | grep -q '"DOCKERHUB_USERNAME"'; then
echo "Docker Hub secrets already exist. Skipping."
else
HUB_AUTH=$(jq '.auths."https://index.docker.io/v1/".auth' -r ~/.docker/config.json | base64 -d -)
USERNAME=$(echo $$HUB_AUTH | cut -d: -f1)
TOKEN=$(echo $$HUB_AUTH | cut -d: -f2)
curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKERHUB_USERNAME -H "Content-Type: application/json" -d '{"data":"'"$$USERNAME"'"}'
curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/DOCKERHUB_PASSWORD -H "Content-Type: application/json" -d '{"data":"'"$$TOKEN"'"}'
echo "Docker Hub secrets created."
unset HUB_AUTH
unset USERNAME
unset TOKEN
fi
# Create KUBECONFIG secret
if echo "$$SECRETS_JSON" | grep -q '"KUBECONFIG"'; then
echo "KUBECONFIG secret already exists. Skipping."
else
KUBE_CONFIG=$(cat /kubeconfig/kubeconfig.yaml | sed 's/127.0.0.1/k8s.dockerlabs.xyz/g')
curl -s -u 'moby:moby1234' -X PUT http://git.dockerlabs.xyz/api/v1/repos/moby/demo-app/actions/secrets/KUBECONFIG -H "Content-Type: application/json" -d '{"data":"'"$(echo -n "$$KUBE_CONFIG")"'"}'
echo "KUBECONFIG secret created."
fi
workspace-git-setup:
content: |
#!/bin/bash
# Configure the SSH key for git access
sudo mkdir -p /home/coder/.ssh
sudo cp /gitea-outputs/id_rsa /home/coder/.ssh/id_rsa
sudo cp /gitea-outputs/id_rsa.pub /home/coder/.ssh/id_rsa.pub
sudo chown -R coder:coder /home/coder/.ssh
# Setup git config
ssh-keyscan -H git.dockerlabs.xyz >> /home/coder/.ssh/known_hosts
git config --global init.defaultBranch main
git config --global user.name 'Moby'
git config --global user.email 'moby@local'
# Commit the initial Labspace project to the git repo
cd /home/coder/project
# Ensure .gitignore exists
touch .gitignore
# Function to safely append to gitignore
add_to_gitignore() {
local entry="$$1"
# Skip if entry already exists
if grep -qxF "$$entry" .gitignore 2>/dev/null; then
return
fi
# Ensure file ends with newline before appending (sed adds it if missing)
sed -i -e '$$a\' .gitignore 2>/dev/null || true
# Now append the entry
echo "$$entry" >> .gitignore
}
rm -rf .git
git init
git remote add origin git@git.dockerlabs.xyz:moby/demo-app.git
git add -A
git commit -m 'Initial commit'
git push -u origin main
gitea-bootstrap:
content: |
#!/bin/bash
echo "Running"
while ! curl -sL http://localhost:3000/user/login > /dev/null; do
sleep 2
done
echo "Gitea is up - proceeding with bootstrap"
# Create the 'moby' user if it doesn't exist
users=$(su-exec git /usr/local/bin/gitea admin user list)
if echo "$$users" | grep -q '^moby '; then
echo "User 'moby' already exists. Skipping account creation."
else
su-exec git /usr/local/bin/gitea admin user create --username moby --password 'moby1234' --email admin@local --admin --must-change-password=false
while ! curl -sL -u 'moby:moby1234' http://localhost:3000/api/v1/user > /dev/null; do
echo "Waiting for user to be fully ready to use..."
sleep 2
done
fi
# Check if demo-app repo exists. Create it if it doesn't.
repo_status=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/api/v1/repos/moby/demo-app)
if [ "$$repo_status" = "404" ]; then
echo "Repository 'moby/demo-app' not found. Creating it..."
curl -s -H "Content-type: application/json" -u 'moby:moby1234' -d '{"name":"demo-app","private":false}' http://localhost:3000/api/v1/user/repos
echo "Repository 'moby/demo-app' created."
else
echo "Repository 'moby/demo-app' already exists. Skipping creation."
fi
# Get and store registration token for the runner
TOKEN_JSON=$(curl -s -u 'moby:moby1234' http://localhost:3000/api/v1/repos/moby/demo-app/actions/runners/registration-token)
REGISTRATION_TOKEN=$(echo $$TOKEN_JSON | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
echo -n $$REGISTRATION_TOKEN > /gitea-outputs/runner_token.txt
# Create SSH key for user 'moby' if it doesn't exist
if [ ! -f /gitea-outputs/id_rsa ]; then
ssh-keygen -t rsa -b 4096 -N "" -f /gitea-outputs/id_rsa
echo "SSH key generated for user 'moby'."
fi
# Add the public key to the user's SSH keys via Gitea API if not already added
keys=$(curl -s -u 'moby:moby1234' http://localhost:3000/api/v1/user/keys)
if echo "$$keys" | grep -q "moby-key"; then
echo "SSH key already added to user 'moby'. Skipping key addition."
else
pub_key=$(cat /gitea-outputs/id_rsa.pub)
curl -s -u 'moby:moby1234' http://localhost:3000/api/v1/user/keys -H "Content-Type: application/json" -d "{\"title\":\"moby-key\",\"key\":\"$$pub_key\"}"
echo "SSH key added to user 'moby'."
fi
# Configure k3s with credentials and TLS settings for the Gitea registry
k3s-registry-config:
content: |
configs:
"registry.dockerlabs.xyz":
auth:
username: moby
password: moby1234
tls:
insecure_skip_verify: true
workspace-kubeconfig-setup:
content: |
#!/bin/bash
set -e
# Setup the kubeconfig for the coder user
mkdir -p ~/.kube
cat /kubeconfig/kubeconfig.yaml | sed 's/127.0.0.1/k8s.dockerlabs.xyz/g' > ~/.kube/config
create-zone-file:
content: |
#!/bin/sh
set -e
IP=$$(dig +short git.dockerlabs.xyz A | grep -E '^[0-9.]+$$' | head -n 1)
mkdir -p /labspace-support/k8s-setup
cat << EOF > /labspace-support/k8s-setup/zone-file-config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns-custom
namespace: kube-system
data:
dockerlabs-xyz.server: |
dockerlabs.xyz:53 {
log
errors
file /etc/coredns/custom/dockerlabs.xyz
}
dockerlabs.xyz: |
\$$ORIGIN dockerlabs.xyz.
\$$TTL 3600
@ IN SOA ns1.dockerlabs.xyz. hostmaster.dockerlabs.xyz. (
2026020601 ; serial (YYYYMMDDnn)
3600 ; refresh
900 ; retry
1209600 ; expire
3600 ; minimum
)
IN NS ns1.dockerlabs.xyz.
; A records
@ IN A $$IP
* IN A $$IP
EOF
workspace-configure-k3s-coredns:
content: |
#!/bin/bash
kubectl apply -f /etc/labspace-support/k8s-setup/zone-file-config-map.yaml
if kubectl -n kube-system get deployment coredns > /dev/null 2>&1; then
kubectl -n kube-system rollout restart deployment/coredns
fi