1+ # Licensed to the Apache Software Foundation (ASF) under one
2+ # or more contributor license agreements. See the NOTICE file
3+ # distributed with this work for additional information
4+ # regarding copyright ownership. The ASF licenses this file
5+ # to you under the Apache License, Version 2.0 (the
6+ # "License"); you may not use this file except in compliance
7+ # with the License. You may obtain a copy of the License at
8+ #
9+ # http://www.apache.org/licenses/LICENSE-2.0
10+ #
11+ # Unless required by applicable law or agreed to in writing,
12+ # software distributed under the License is distributed on an
13+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+ # KIND, either express or implied. See the License for the
15+ # specific language governing permissions and limitations
16+ # under the License.
17+
18+ name : " Start and Prepare DinD"
19+ description : " Launch, verify, and prepare a Docker-in-Docker environment."
20+ inputs :
21+ # --- Core DinD Config ---
22+ container-name :
23+ description : " Name for the DinD container."
24+ default : dind-daemon
25+ bind-address :
26+ default : 127.0.0.1
27+ port :
28+ default : " 2375"
29+ storage-volume :
30+ default : dind-storage
31+ execroot-volume :
32+ default : dind-execroot
33+ ephemeral-volumes :
34+ description : " Generate unique per-run volume names (recommended)."
35+ default : " true"
36+ auto-prune-dangling :
37+ description : " Prune dangling ephemeral DinD volumes from previous runs."
38+ default : " true"
39+ tmpfs-run-size :
40+ default : 1g
41+ tmpfs-varrun-size :
42+ default : 1g
43+ storage-driver :
44+ default : overlay2
45+ additional-dockerd-args :
46+ default : " "
47+ use-host-network :
48+ description : " Run DinD with --network host instead of publishing a TCP port."
49+ default : " false"
50+
51+ # --- Health & Wait Config ---
52+ health-interval :
53+ default : 2s
54+ health-retries :
55+ default : " 60"
56+ health-start-period :
57+ default : 10s
58+ wait-timeout :
59+ default : " 180"
60+
61+ # --- NEW: Optional Setup & Verification Steps ---
62+ cleanup-dind-on-start :
63+ description : " Run 'docker system prune' inside DinD immediately after it starts."
64+ default : " true"
65+ smoke-test-port-mapping :
66+ description : " Run a quick test to ensure port mapping from DinD is working."
67+ default : " true"
68+ prime-testcontainers :
69+ description : " Start and stop a small container via the testcontainers library to prime Ryuk."
70+ default : " false"
71+
72+ # --- Output Config ---
73+ export-gh-env :
74+ description : " Also write DOCKER_HOST and DIND_IP to $GITHUB_ENV for the rest of the job."
75+ default : " false"
76+
77+ outputs :
78+ docker-host :
79+ description : " The TCP address for the DinD daemon (e.g., tcp://127.0.0.1:2375)."
80+ value : ${{ steps.set-output.outputs.docker-host }}
81+ dind-ip :
82+ description : " The discovered bridge IP address of the DinD container."
83+ value : ${{ steps.discover-ip.outputs.dind-ip }}
84+ container-name :
85+ description : " The name of the running DinD container."
86+ value : ${{ inputs.container-name || 'dind-daemon' }}
87+ storage-volume :
88+ value : ${{ steps.set-output.outputs.storage_volume }}
89+ execroot-volume :
90+ value : ${{ steps.set-output.outputs.execroot_volume }}
91+
92+ runs :
93+ using : " composite"
94+ steps :
95+ - name : Prune old dangling ephemeral DinD volumes
96+ if : ${{ inputs.auto-prune-dangling == 'true' }}
97+ shell : bash
98+ run : |
99+ docker volume ls -q \
100+ --filter "label=com.github.dind=1" \
101+ --filter "label=com.github.repo=${GITHUB_REPOSITORY}" \
102+ --filter "dangling=true" | xargs -r docker volume rm || true
103+
104+ - name : Start docker:dind
105+ shell : bash
106+ run : |
107+ # (Your original 'Start docker:dind' script is perfect here - no changes needed)
108+ set -euo pipefail
109+ NAME="${{ inputs.container-name || 'dind-daemon' }}"
110+ BIND="${{ inputs.bind-address || '127.0.0.1' }}"
111+ PORT="${{ inputs.port || '2375' }}"
112+ SD="${{ inputs.storage-driver || 'overlay2' }}"
113+ TRS="${{ inputs.tmpfs-run-size || '1g' }}"
114+ TVRS="${{ inputs.tmpfs-varrun-size || '1g' }}"
115+ HI="${{ inputs.health-interval || '2s' }}"
116+ HR="${{ inputs.health-retries || '60' }}"
117+ HSP="${{ inputs.health-start-period || '10s' }}"
118+ EXTRA="${{ inputs.additional-dockerd-args }}"
119+ USE_HOST_NET="${{ inputs.use-host-network || 'false' }}"
120+
121+ if [[ "${{ inputs.ephemeral-volumes }}" == "true" ]]; then
122+ SUFFIX="${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-job}"
123+ STORAGE_VOL="dind-storage-${SUFFIX}"
124+ EXECROOT_VOL="dind-execroot-${SUFFIX}"
125+ else
126+ STORAGE_VOL="${{ inputs.storage-volume || 'dind-storage' }}"
127+ EXECROOT_VOL="${{ inputs.execroot-volume || 'dind-execroot' }}"
128+ fi
129+
130+ docker volume create --name "${STORAGE_VOL}" --label "com.github.dind=1" --label "com.github.repo=${GITHUB_REPOSITORY}" >/dev/null
131+ docker volume create --name "${EXECROOT_VOL}" --label "com.github.dind=1" --label "com.github.repo=${GITHUB_REPOSITORY}" >/dev/null
132+ docker rm -f -v "$NAME" 2>/dev/null || true
133+
134+ NET_ARGS=""
135+ PUBLISH_ARGS="-p ${BIND}:${PORT}:${PORT}"
136+ if [[ "${USE_HOST_NET}" == "true" ]]; then
137+ NET_ARGS="--network host"
138+ PUBLISH_ARGS=""
139+ fi
140+
141+ docker run -d --privileged --name "$NAME" \
142+ --cgroupns=host \
143+ -e DOCKER_TLS_CERTDIR= \
144+ ${NET_ARGS} \
145+ ${PUBLISH_ARGS} \
146+ -v "${STORAGE_VOL}:/var/lib/docker" \
147+ -v "${EXECROOT_VOL}:/execroot" \
148+ --tmpfs /run:rw,exec,size=${TRS} \
149+ --tmpfs /var/run:rw,exec,size=${TVRS} \
150+ --label "com.github.dind=1" \
151+ --health-cmd='docker info > /dev/null' \
152+ --health-interval=${HI} \
153+ --health-retries=${HR} \
154+ --health-start-period=${HSP} \
155+ docker:dind \
156+ --host=tcp://0.0.0.0:${PORT} \
157+ --host=unix:///var/run/docker.sock \
158+ --storage-driver=${SD} \
159+ --exec-root=/execroot ${EXTRA}
160+
161+ {
162+ echo "STORAGE_VOL=${STORAGE_VOL}"
163+ echo "EXECROOT_VOL=${EXECROOT_VOL}"
164+ } >> "$GITHUB_ENV"
165+
166+ - name : Wait for DinD daemon
167+ shell : bash
168+ run : |
169+ set -euo pipefail
170+ NAME="${{ inputs.container-name || 'dind-daemon' }}"
171+ HOST="${{ inputs.bind-address || '127.0.0.1' }}"
172+ PORT="${{ inputs.port || '2375' }}"
173+ TIMEOUT="${{ inputs.wait-timeout || '180' }}"
174+ echo "Waiting for Docker-in-Docker to be ready..."
175+ if ! timeout ${TIMEOUT}s bash -c 'until docker -H "tcp://'"${HOST}"':'"${PORT}"'" info >/dev/null 2>&1; do sleep 2; done'; then
176+ echo "::error::DinD failed to start within ${TIMEOUT}s."
177+ docker logs "$NAME" || true
178+ exit 1
179+ fi
180+ echo "DinD is ready."
181+ docker -H "tcp://${HOST}:${PORT}" info --format 'Daemon OK → OS={{.OperatingSystem}} Version={{.ServerVersion}}'
182+
183+ - id : set-output
184+ shell : bash
185+ run : |
186+ HOST="${{ inputs.bind-address || '127.0.0.1' }}"
187+ PORT="${{ inputs.port || '2375' }}"
188+ echo "docker-host=tcp://${HOST}:${PORT}" >> "$GITHUB_OUTPUT"
189+ echo "storage_volume=${STORAGE_VOL:-}" >> "$GITHUB_OUTPUT"
190+ echo "execroot_volume=${EXECROOT_VOL:-}" >> "$GITHUB_OUTPUT"
191+
192+ # --- NEW: Integrated Setup & Verification Steps ---
193+
194+ - name : Cleanup DinD Environment
195+ if : ${{ inputs.cleanup-dind-on-start == 'true' }}
196+ shell : bash
197+ run : |
198+ echo "Performing initial cleanup of DinD environment..."
199+ DIND_HOST="${{ steps.set-output.outputs.docker-host }}"
200+ docker -H "${DIND_HOST}" system prune -af --volumes
201+ docker -H "${DIND_HOST}" image prune -af
202+
203+ - id : discover-ip
204+ name : Discover DinD Container IP
205+ shell : bash
206+ run : |
207+ set -euo pipefail
208+ NAME="${{ inputs.container-name || 'dind-daemon' }}"
209+
210+ # Use host daemon to inspect the DinD container
211+ nm=$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$NAME")
212+ echo "DinD NetworkMode=${nm}"
213+
214+ # Try to find the bridge network IP
215+ ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$NAME" || true)
216+
217+ # If still empty, likely host networking -> use loopback
218+ if [[ -z "${ip}" || "${nm}" == "host" ]]; then
219+ echo "No bridge IP found or using host network. Falling back to 127.0.0.1."
220+ ip="127.0.0.1"
221+ fi
222+
223+ echo "Discovered DinD IP: ${ip}"
224+ echo "dind-ip=${ip}" >> "$GITHUB_OUTPUT"
225+
226+ - name : Smoke Test Port Mapping
227+ if : ${{ inputs.smoke-test-port-mapping == 'true' }}
228+ env :
229+ DOCKER_HOST : ${{ steps.set-output.outputs.docker-host }}
230+ DIND_IP : ${{ steps.discover-ip.outputs.dind-ip }}
231+ shell : bash
232+ run : |
233+ set -euo pipefail
234+ echo "Running port mapping smoke test..."
235+ docker pull redis:7.2-alpine
236+ cid=$(docker run -d -p 0:6379 --name redis-smoke redis:7-alpine)
237+ hostport=$(docker port redis-smoke 6379/tcp | sed 's/.*://')
238+ echo "Redis container started, mapped to host port ${hostport}"
239+ echo "Probing connection to ${DIND_IP}:${hostport} ..."
240+
241+ timeout 5 bash -c 'exec 3<>/dev/tcp/$DIND_IP/'"$hostport"
242+ if [[ $? -eq 0 ]]; then
243+ echo "TCP connection successful. Port mapping is working."
244+ else
245+ echo "::error::Failed to connect to mapped port on ${DIND_IP}:${hostport}"
246+ docker logs redis-smoke
247+ exit 1
248+ fi
249+ docker rm -f "$cid"
250+
251+ - name : Prime Testcontainers (Ryuk)
252+ if : ${{ inputs.prime-testcontainers == 'true' }}
253+ env :
254+ DOCKER_HOST : ${{ steps.set-output.outputs.docker-host }}
255+ TESTCONTAINERS_HOST_OVERRIDE : ${{ steps.discover-ip.outputs.dind-ip }}
256+ shell : bash
257+ run : |
258+ echo "Priming Testcontainers/Ryuk..."
259+ python -m pip install -q --upgrade pip testcontainers
260+ # Use a tiny image for a fast and stable prime
261+ docker pull alpine:3.19
262+ python - <<'PY'
263+ from testcontainers.core.container import DockerContainer
264+ c = DockerContainer("alpine:3.19").with_command("true")
265+ c.start()
266+ c.stop()
267+ print("Ryuk primed and ready.")
268+ PY
269+
270+ - name : Export Environment Variables
271+ if : ${{ inputs.export-gh-env == 'true' }}
272+ shell : bash
273+ run : |
274+ echo "DOCKER_HOST=${{ steps.set-output.outputs.docker-host }}" >> "$GITHUB_ENV"
275+ echo "DIND_IP=${{ steps.discover-ip.outputs.dind-ip }}" >> "$GITHUB_ENV"
0 commit comments