Summary
The PDF-conversion flow the tutorial documents (pandoc card.md -o card.pdf --pdf-engine=typst) fails on real, deployed Cloudflare Containers with:
pandoc: openFile: resource busy (file is locked)
HasCallStack backtrace:
bracket, called at ./System/IO/Temp.hs:114:3 in temporary-1.3-…:System.IO.Temp
The same command works under wrangler dev (local Docker), so the failure is invisible until first deploy. The trigger is narrow: it is specifically the --pdf-engine path's temp-file staging (System.IO.Temp / the temporary package, i.e. GHC's file locking on the temp file). Plain pandoc output files and ordinary shell writes to the same filesystem are fine.
Reproduction
examples/container from this repo at 76d9e75, deployed unmodified except for two Dockerfile changes: computerd tag bumped to 0.1.1 (to match the published @cloudflare/computer@0.1.1 the example was installed with), and pandoc 3.10.1 + typst 0.15.1 added to the image (same tools the tutorial uses). wrangler 4.119, instance type standard-2.
All probes are plain curl against the deployed example's own HTTP surface:
Control — the filesystem accepts writes (shell):
POST /c/repro1/exec {"command":"sh -c 'echo ctrl > /tmp/ctrl.txt && cat /tmp/ctrl.txt'"}
→ {"status":"completed","exitCode":0,"stdout":"ctrl\n"}
Control — plain pandoc output file works (no engine involved):
POST /c/repro1/exec {"command":"printf \"# hi\\n\" | pandoc -f markdown -t docx -o /tmp/out.docx -"}
→ {"status":"completed","exitCode":0}
Failure — any --pdf-engine invocation, container-local /tmp:
POST /c/repro1/exec {"command":"printf \"# hi\\n\" | pandoc -f markdown -o /tmp/out.pdf --pdf-engine=typst -"}
→ {"status":"failed","exitCode":1,"stderr":"pandoc: openFile: resource busy (file is locked)\nHasCallStack backtrace:\n bracket, called at ./System/IO/Temp.hs:114:3 …"}
Failure — the tutorial's exact flow (input written host-side, synced to the mount — note pushed: 2, sync itself works fine):
PUT /c/repro1/file/workspace/card.md ("# Karta…")
POST /c/repro1/exec {"command":"pandoc /workspace/card.md -o /workspace/card.pdf --pdf-engine=typst"}
→ {"status":"failed","exitCode":1,"stderr":"pandoc: openFile: resource busy (file is locked)…","pushed":2,"sync":{"status":"complete","applied":3}}
Analysis
GHC takes an fcntl lock when System.IO.Temp opens the temp file that the --pdf-engine path stages the intermediate document into, and the deployed Container filesystem refuses that lock (EBUSY-shaped failure surfaced as "resource busy (file is locked)"). Local Docker's filesystem grants the lock, hence the dev/prod split. Ordinary openFile for pandoc's --output does not trip it (the docx control above), so the blast radius is exactly "anything that goes through pandoc's PDF-engine temp staging".
Working workaround (verified on the same deployment)
Skip --pdf-engine entirely: have pandoc emit standalone typst markup to stdout (shell owns the file), then compile with typst (Rust — no GHC locking):
POST /c/repro1/exec {"command":"printf \"# hi\\n\" | pandoc -f markdown -t typst -s - > /tmp/x.typ && typst compile /tmp/x.typ /tmp/x.pdf && ls -la /tmp/x.pdf"}
→ {"status":"completed","exitCode":0,"stdout":"-rw-r--r-- 1 root root 7004 … /tmp/x.pdf\n"}
Possibly worth a note in the tutorial until the underlying lock behavior is addressed on the Containers filesystem — as-is, the tutorial's flagship command works in every local run and fails on the first production deploy, which is an expensive way to find out.
Happy to provide the deployed-example wrangler config or run further probes if useful.
Summary
The PDF-conversion flow the tutorial documents (
pandoc card.md -o card.pdf --pdf-engine=typst) fails on real, deployed Cloudflare Containers with:The same command works under
wrangler dev(local Docker), so the failure is invisible until first deploy. The trigger is narrow: it is specifically the--pdf-enginepath's temp-file staging (System.IO.Temp/ thetemporarypackage, i.e. GHC's file locking on the temp file). Plain pandoc output files and ordinary shell writes to the same filesystem are fine.Reproduction
examples/containerfrom this repo at76d9e75, deployed unmodified except for two Dockerfile changes: computerd tag bumped to0.1.1(to match the published@cloudflare/computer@0.1.1the example was installed with), andpandoc 3.10.1+typst 0.15.1added to the image (same tools the tutorial uses).wrangler 4.119, instance typestandard-2.All probes are plain
curlagainst the deployed example's own HTTP surface:Control — the filesystem accepts writes (shell):
Control — plain pandoc output file works (no engine involved):
Failure — any
--pdf-engineinvocation, container-local /tmp:Failure — the tutorial's exact flow (input written host-side, synced to the mount — note
pushed: 2, sync itself works fine):Analysis
GHC takes an fcntl lock when
System.IO.Tempopens the temp file that the--pdf-enginepath stages the intermediate document into, and the deployed Container filesystem refuses that lock (EBUSY-shaped failure surfaced as "resource busy (file is locked)"). Local Docker's filesystem grants the lock, hence the dev/prod split. OrdinaryopenFilefor pandoc's--outputdoes not trip it (the docx control above), so the blast radius is exactly "anything that goes through pandoc's PDF-engine temp staging".Working workaround (verified on the same deployment)
Skip
--pdf-engineentirely: have pandoc emit standalone typst markup to stdout (shell owns the file), then compile with typst (Rust — no GHC locking):Possibly worth a note in the tutorial until the underlying lock behavior is addressed on the Containers filesystem — as-is, the tutorial's flagship command works in every local run and fails on the first production deploy, which is an expensive way to find out.
Happy to provide the deployed-example wrangler config or run further probes if useful.