Repository navigation
Closed
Description
Goal
Run student code on a separate Docker host (a Fly.io machine over WireGuard) instead of the web server. Changing provider later means changing DOCKER_HOST and the ports host, nothing else.
Tasks
- Composer on the web server, not in the sandbox: Run composer on the host, not in the sandbox #342, Run students' composer on the web server, not in the sandbox phpschool.io#367
- Ports known up front, address asked from the execution: Exercises pick their port up front, docker picks where it is published #343, feat: claim published sandbox ports in Redis phpschool.io#368 (Redis
LockPortPicker) - Sandbox per solution, port picked by the sandbox: feat: sandbox per solution, made by a SandboxFactory #345, fix: update workshops for the sandbox factory phpschool.io#370, learn-you-php Time Server php-school/learn-you-php@834b6dd
- One container per solution (php-workshop), see below. Replaces draft Copy files into one container per solution, docker exec each run #341.
- Move to Fly: machine config, block outbound on the ports network, permanent WireGuard on imp, switch production. Drop the
/solution/vendorand/composermkdirs from the workshop images.
Design now (php-workshop src/Process)
Sandbox\SandboxFactory: one for the app,HostSandboxFactoryorDockerSandboxFactory. Holds the setup (docker binary, image, network, ports host, port picker).create($directory, $scenario)gives aSandbox.Sandbox\Sandbox: one per solution, reference and student. Holds the directory and the port.execute(ProcessInput)gives aSandboxExecution,getPort()the port.Sandbox\SandboxExecution: one run, a single class. start, wait, output,getAddress().Sandbox\ProcessInput: just the command, executable, args, env, stdin.Port\PortPicker:FreePortPickerlocally,LockPortPicker(Redis lock, 60s, 5 tries) on the website.
The exercise says ->exposePort(). The factory picks one port per solution and Docker publishes it the same inside and out, -p host:N:N. The exercise puts it in its args on the pre events with $event->getSandbox()->getPort() and connects on the executing events with $event->getExecution()->getAddress().
Next: one container per solution
DockerSandboxFactory::create()starts a container for the solution with the port published, and copies the files in once:find . ! -type d | tar -c -T - | docker exec -i c tar -x -C /solution(files only, tar can't chmod image-owned dirs),-v /solutionanonymous volume.DockerSandbox::execute()is adocker execwithtimeout -s KILL 30.- When the sandbox is done the container is removed (
rm -f -v) and the port lock released, so the lock no longer needs a fixed 60s. - The old prototype (local branch
exec-per-folder) cut verify from ~6s to ~2.5s. Rewrite it on the new classes rather than rebasing.
Notes
--internalDocker networks can't publish ports.- An IPv6 ports host is passed already bracketed, eg.
[fdaa::2]. - The required "Conventional title" check didn't run on fix: update workshops for the sandbox factory phpschool.io#370, worth checking
pr-title.yml.
Metadata
Metadata
Assignees
Labels
No labels
Activity
feat: free a sandbox's port when it finishes (#347)
feat: checks name the solution files they read (#349)
feat: one container per solution, each run a docker exec (#346)
chore: update php-workshop for one container per solution (#371)
feat: the Fly machine student code runs on (#373)
feat: release the remote sandbox (#375)
AydinHassan commentedon Nov 1, 2026
Live on phpschool.io: student code runs on the Fly.io sandbox, one container per solution.