@@ -42,13 +42,28 @@ services:
4242 - graphql-engine
4343 - serve
4444
45+ # Untrusted-input compilers. Cap resources and drop privileges. Not read_only:
46+ # these compilers write intermediate files; confirm a writable path per image
47+ # before tightening further.
4548 z88dk :
4649 image : ghcr.io/stever/zxcoder-api-z88dk
4750 restart : always
51+ mem_limit : 512m
52+ cpus : 1.0
53+ pids_limit : 256
54+ cap_drop : [ALL]
55+ security_opt :
56+ - no-new-privileges:true
4857
4958 zxbasic :
5059 image : ghcr.io/stever/zxcoder-api-zxbasic
5160 restart : always
61+ mem_limit : 512m
62+ cpus : 1.0
63+ pids_limit : 256
64+ cap_drop : [ALL]
65+ security_opt :
66+ - no-new-privileges:true
5267
5368 # Headless emulator: compiles a program to .tap and renders it running to
5469 # GIF/MP4. Renders inline BASIC, or a public project looked up from Hasura
@@ -64,6 +79,36 @@ services:
6479 - hasura
6580 environment :
6681 HASURA_URL : http://hasura:8080/v1/graphql
82+ # Untrusted execution: cap CPU/mem/processes and drop privileges. Root FS is
83+ # read-only with a writable tmpfs for the temp mp4/f32le files it creates.
84+ # NOTE: verify `user: node` + `read_only` on first deploy — the image must
85+ # let the node user read /app and tsx must cache under /tmp.
86+ mem_limit : 1g
87+ cpus : 2.0
88+ pids_limit : 512
89+ user : node
90+ read_only : true
91+ tmpfs :
92+ - /tmp
93+ cap_drop : [ALL]
94+ security_opt :
95+ - no-new-privileges:true
96+ healthcheck :
97+ test : ["CMD", "node", "-e", "fetch('http://localhost:5001/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
98+ interval : 30s
99+ timeout : 5s
100+ retries : 3
101+ start_period : 20s
102+ # NETWORK ISOLATION (prod): gif-service + zxbasic + z88dk need no inbound
103+ # internet, only intra-stack traffic (gif-service -> hasura; hasura ->
104+ # zxbasic/z88dk). In the prod compose put them on an `internal: true`
105+ # network that also carries hasura. Not wired here to avoid breaking dev
106+ # connectivity that can't be validated in this repo.
107+ #
108+ # AUTO-RESTART ON HANG: plain `restart: always` restarts on exit, NOT on an
109+ # unhealthy healthcheck. To recover a wedged event loop automatically, run
110+ # an autoheal sidecar (or Swarm). With compile now isolated in a killable
111+ # child and the render wall-clock guard, the remaining wedge risk is small.
67112
68113 # GoToSocial: the bot's federated identity, served at social.zxplay.org but
69114 # presenting handles as @user@zxplay.org via the account-domain webfinger trick.
0 commit comments