Skip to content

Commit 421e607

Browse files
cuihtlauacclaude
andcommitted
Split TSan into separate image for lighter default and Codespace support
The main devcontainer image now ships a single "ocaml" switch (~4.5 GB), making it Codespace-friendly. TSan is available as a separate "ocaml-devcontainer-tsan" image (~7.5 GB) that adds the "ocaml+tsan" switch on top. Key changes: - Rename switches: 5.4.0 → ocaml, 5.4.0+tsan → ocaml+tsan - New tsan/Dockerfile layered on top of the dev image - New .devcontainer-tsan/ configs for pre-built and local-build TSan - Move fix-tsan-aslr.sh from .devcontainer/ to .devcontainer-tsan/ - Remove ASLR sysctl from base image build (only tsan/ needs it) - New build-push-tsan.yml workflow triggered after main image builds - Test matrix: single "ocaml" for main image, [ocaml, ocaml+tsan] for TSan Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 05d97d6 commit 421e607

22 files changed

Lines changed: 580 additions & 198 deletions

File tree

.devcontainer-from-scratch/devcontainer.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@
2121
"settings": {
2222
"ocaml.sandbox": {
2323
"kind": "opam",
24-
"switch": "5.4.0"
24+
"switch": "ocaml"
2525
}
2626
}
2727
}
2828
},
2929

30-
"postStartCommand": "bash .devcontainer/fix-tsan-aslr.sh",
31-
3230
"remoteUser": "vscode",
3331

3432
"mounts": [
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "OCaml Development (TSan, Local Build)",
3+
4+
"build": {
5+
"dockerfile": "../tsan/Dockerfile",
6+
"context": "..",
7+
"args": {
8+
"BASE_IMAGE": "ocaml-devcontainer:latest"
9+
}
10+
},
11+
12+
"features": {
13+
"ghcr.io/anthropics/devcontainer-features/claude-code:1": {}
14+
},
15+
16+
"customizations": {
17+
"vscode": {
18+
"extensions": [
19+
"ocamllabs.ocaml-platform"
20+
],
21+
"settings": {
22+
"ocaml.sandbox": {
23+
"kind": "opam",
24+
"switch": "ocaml"
25+
}
26+
}
27+
}
28+
},
29+
30+
"postStartCommand": "bash .devcontainer-tsan/fix-tsan-aslr.sh",
31+
32+
"remoteUser": "vscode",
33+
34+
"mounts": [
35+
"source=dune-cache,target=/home/vscode/.cache/dune,type=volume"
36+
],
37+
38+
"remoteEnv": {
39+
"DUNE_CACHE_ROOT": "/home/vscode/.cache/dune"
40+
},
41+
42+
"hostRequirements": {
43+
"cpus": 4,
44+
"memory": "8gb",
45+
"storage": "32gb"
46+
}
47+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "OCaml Development (TSan)",
3+
// Pre-built image from Docker Hub. Also available on GHCR:
4+
// ghcr.io/tarides/ocaml-devcontainer-tsan:latest
5+
"image": "cuihtlauac/ocaml-devcontainer-tsan:latest",
6+
7+
"features": {
8+
"ghcr.io/anthropics/devcontainer-features/claude-code:1": {}
9+
},
10+
11+
"customizations": {
12+
"vscode": {
13+
"extensions": [
14+
"ocamllabs.ocaml-platform"
15+
],
16+
"settings": {
17+
"ocaml.sandbox": {
18+
"kind": "opam",
19+
"switch": "ocaml"
20+
}
21+
}
22+
}
23+
},
24+
25+
"postStartCommand": "bash .devcontainer-tsan/fix-tsan-aslr.sh",
26+
27+
"remoteUser": "vscode",
28+
29+
"mounts": [
30+
"source=dune-cache,target=/home/vscode/.cache/dune,type=volume"
31+
],
32+
33+
"remoteEnv": {
34+
"DUNE_CACHE_ROOT": "/home/vscode/.cache/dune"
35+
},
36+
37+
"hostRequirements": {
38+
"cpus": 4,
39+
"memory": "8gb",
40+
"storage": "32gb"
41+
}
42+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
2-
# Fix ASLR entropy for TSan-instrumented binaries (OCaml 5.4.0+tsan switch).
2+
# Fix ASLR entropy for TSan-instrumented binaries (ocaml+tsan switch).
33
# TSan requires vm.mmap_rnd_bits <= 28; modern kernels default to 32.
44
# See: https://github.com/google/sanitizers/issues/1716
55

@@ -19,7 +19,7 @@ fi
1919
if setarch "$(uname -m)" --addr-no-randomize /bin/true 2>/dev/null; then
2020
sudo tee /etc/profile.d/fix-tsan-aslr.sh >/dev/null <<'EOF'
2121
# Disable ASLR so ThreadSanitizer-instrumented binaries can run.
22-
# Installed by .devcontainer/fix-tsan-aslr.sh — safe to remove.
22+
# Installed by .devcontainer-tsan/fix-tsan-aslr.sh — safe to remove.
2323
if [ -z "$TSAN_ASLR_FIXED" ]; then
2424
mmap_rnd_bits=$(cat /proc/sys/vm/mmap_rnd_bits 2>/dev/null || echo 0)
2525
if [ "$mmap_rnd_bits" -gt 28 ]; then
@@ -33,4 +33,4 @@ EOF
3333
fi
3434

3535
echo "WARNING: Cannot fix ASLR entropy for TSan (vm.mmap_rnd_bits=$MMAP_RND_BITS)."
36-
echo "The 5.4.0+tsan switch may not work. See: https://github.com/google/sanitizers/issues/1716"
36+
echo "The ocaml+tsan switch may not work. See: https://github.com/google/sanitizers/issues/1716"

.devcontainer/devcontainer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
"settings": {
1717
"ocaml.sandbox": {
1818
"kind": "opam",
19-
"switch": "5.4.0"
19+
"switch": "ocaml"
2020
}
2121
}
2222
}
2323
},
2424

25-
"postStartCommand": "bash .devcontainer/fix-tsan-aslr.sh; bash .devcontainer/hide-codespaces-badge.sh",
25+
"postStartCommand": "bash .devcontainer/hide-codespaces-badge.sh",
2626

2727
"remoteUser": "vscode",
2828

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
name: Build and Push TSan Image
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ['v*']
7+
paths:
8+
- 'tsan/**'
9+
- '.github/workflows/build-push-tsan.yml'
10+
pull_request:
11+
branches: [main]
12+
paths:
13+
- 'tsan/**'
14+
workflow_run:
15+
workflows: ["Build and Push Images"]
16+
types: [completed]
17+
branches: [main]
18+
workflow_dispatch:
19+
20+
env:
21+
DOCKER_HUB_TSAN: ${{ secrets.DOCKERHUB_USERNAME }}/ocaml-devcontainer-tsan
22+
GHCR_TSAN: ghcr.io/${{ github.repository_owner }}/ocaml-devcontainer-tsan
23+
GHCR_DEV: ghcr.io/${{ github.repository_owner }}/ocaml-devcontainer
24+
25+
jobs:
26+
# ============================================================================
27+
# Build TSan Images (parallel per architecture)
28+
# ============================================================================
29+
build-tsan-amd64:
30+
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
31+
runs-on: ubuntu-latest
32+
permissions:
33+
contents: read
34+
packages: write
35+
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v6
39+
40+
- name: Reduce ASLR entropy for TSan build
41+
run: sudo sysctl -w vm.mmap_rnd_bits=28
42+
43+
- name: Set up Docker Buildx
44+
uses: docker/setup-buildx-action@v3
45+
46+
- name: Login to Docker Hub
47+
if: github.event_name != 'pull_request'
48+
uses: docker/login-action@v3
49+
with:
50+
username: ${{ secrets.DOCKERHUB_USERNAME }}
51+
password: ${{ secrets.DOCKERHUB_TOKEN }}
52+
53+
- name: Login to GitHub Container Registry
54+
if: github.event_name != 'pull_request'
55+
uses: docker/login-action@v3
56+
with:
57+
registry: ghcr.io
58+
username: ${{ github.actor }}
59+
password: ${{ secrets.GITHUB_TOKEN }}
60+
61+
- name: Build and push TSan image (amd64)
62+
uses: docker/build-push-action@v6
63+
with:
64+
context: ./tsan
65+
file: ./tsan/Dockerfile
66+
platforms: linux/amd64
67+
push: ${{ github.event_name != 'pull_request' }}
68+
tags: |
69+
${{ env.GHCR_TSAN }}:latest-amd64
70+
${{ env.DOCKER_HUB_TSAN }}:latest-amd64
71+
labels: |
72+
org.opencontainers.image.source=https://github.com/${{ github.repository }}
73+
org.opencontainers.image.revision=${{ github.sha }}
74+
build-args: |
75+
BASE_IMAGE=${{ env.GHCR_DEV }}:latest-amd64
76+
cache-from: type=gha,scope=tsan-amd64
77+
cache-to: type=gha,mode=max,scope=tsan-amd64
78+
79+
build-tsan-arm64:
80+
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
81+
runs-on: ubuntu-24.04-arm
82+
permissions:
83+
contents: read
84+
packages: write
85+
86+
steps:
87+
- name: Checkout
88+
uses: actions/checkout@v6
89+
90+
- name: Reduce ASLR entropy for TSan build
91+
run: sudo sysctl -w vm.mmap_rnd_bits=28
92+
93+
- name: Set up Docker Buildx
94+
uses: docker/setup-buildx-action@v3
95+
96+
- name: Login to Docker Hub
97+
if: github.event_name != 'pull_request'
98+
uses: docker/login-action@v3
99+
with:
100+
username: ${{ secrets.DOCKERHUB_USERNAME }}
101+
password: ${{ secrets.DOCKERHUB_TOKEN }}
102+
103+
- name: Login to GitHub Container Registry
104+
if: github.event_name != 'pull_request'
105+
uses: docker/login-action@v3
106+
with:
107+
registry: ghcr.io
108+
username: ${{ github.actor }}
109+
password: ${{ secrets.GITHUB_TOKEN }}
110+
111+
- name: Build and push TSan image (arm64)
112+
uses: docker/build-push-action@v6
113+
with:
114+
context: ./tsan
115+
file: ./tsan/Dockerfile
116+
platforms: linux/arm64
117+
push: ${{ github.event_name != 'pull_request' }}
118+
tags: |
119+
${{ env.GHCR_TSAN }}:latest-arm64
120+
${{ env.DOCKER_HUB_TSAN }}:latest-arm64
121+
labels: |
122+
org.opencontainers.image.source=https://github.com/${{ github.repository }}
123+
org.opencontainers.image.revision=${{ github.sha }}
124+
build-args: |
125+
BASE_IMAGE=${{ env.GHCR_DEV }}:latest-arm64
126+
cache-from: type=gha,scope=tsan-arm64
127+
cache-to: type=gha,mode=max,scope=tsan-arm64
128+
129+
# ============================================================================
130+
# Merge TSan Image Tags into Multi-Arch Manifest
131+
# ============================================================================
132+
merge-tsan:
133+
needs: [build-tsan-amd64, build-tsan-arm64]
134+
if: |
135+
always() &&
136+
needs.build-tsan-amd64.result == 'success' &&
137+
needs.build-tsan-arm64.result == 'success' &&
138+
github.event_name != 'pull_request'
139+
runs-on: ubuntu-latest
140+
permissions:
141+
contents: read
142+
packages: write
143+
144+
steps:
145+
- name: Set up Docker Buildx
146+
uses: docker/setup-buildx-action@v3
147+
148+
- name: Login to Docker Hub
149+
uses: docker/login-action@v3
150+
with:
151+
username: ${{ secrets.DOCKERHUB_USERNAME }}
152+
password: ${{ secrets.DOCKERHUB_TOKEN }}
153+
154+
- name: Login to GitHub Container Registry
155+
uses: docker/login-action@v3
156+
with:
157+
registry: ghcr.io
158+
username: ${{ github.actor }}
159+
password: ${{ secrets.GITHUB_TOKEN }}
160+
161+
- name: Create and push multi-arch manifest (GHCR)
162+
run: |
163+
docker buildx imagetools create -t ${{ env.GHCR_TSAN }}:latest \
164+
${{ env.GHCR_TSAN }}:latest-amd64 \
165+
${{ env.GHCR_TSAN }}:latest-arm64
166+
167+
# Add version tag if this is a release
168+
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
169+
VERSION=${GITHUB_REF#refs/tags/}
170+
docker buildx imagetools create -t ${{ env.GHCR_TSAN }}:${VERSION} \
171+
${{ env.GHCR_TSAN }}:latest-amd64 \
172+
${{ env.GHCR_TSAN }}:latest-arm64
173+
fi
174+
175+
- name: Create and push multi-arch manifest (Docker Hub)
176+
run: |
177+
docker buildx imagetools create -t ${{ env.DOCKER_HUB_TSAN }}:latest \
178+
${{ env.DOCKER_HUB_TSAN }}:latest-amd64 \
179+
${{ env.DOCKER_HUB_TSAN }}:latest-arm64
180+
181+
# Add version tag if this is a release
182+
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
183+
VERSION=${GITHUB_REF#refs/tags/}
184+
docker buildx imagetools create -t ${{ env.DOCKER_HUB_TSAN }}:${VERSION} \
185+
${{ env.DOCKER_HUB_TSAN }}:latest-amd64 \
186+
${{ env.DOCKER_HUB_TSAN }}:latest-arm64
187+
fi
188+
189+
- name: Verify multi-arch manifest
190+
run: |
191+
echo "=== GHCR TSan Image ==="
192+
docker buildx imagetools inspect ${{ env.GHCR_TSAN }}:latest
193+
echo ""
194+
echo "=== Docker Hub TSan Image ==="
195+
docker buildx imagetools inspect ${{ env.DOCKER_HUB_TSAN }}:latest

.github/workflows/build-push.yml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,6 @@ jobs:
7070
- name: Checkout
7171
uses: actions/checkout@v6
7272

73-
- name: Reduce ASLR entropy for TSan build
74-
# TSan requires lower ASLR entropy to avoid memory mapping collisions
75-
# Native runners allow this sysctl to take effect (unlike BuildKit containers)
76-
# See: https://github.com/google/sanitizers/issues/1716
77-
run: sudo sysctl -w vm.mmap_rnd_bits=28
78-
7973
- name: Set up Docker Buildx
8074
uses: docker/setup-buildx-action@v3
8175

@@ -126,9 +120,6 @@ jobs:
126120
- name: Checkout
127121
uses: actions/checkout@v6
128122

129-
- name: Reduce ASLR entropy for TSan build
130-
run: sudo sysctl -w vm.mmap_rnd_bits=28
131-
132123
- name: Set up Docker Buildx
133124
uses: docker/setup-buildx-action@v3
134125

0 commit comments

Comments
 (0)