Module 3 · Reading · about 25 min

Translate a Compose file

See what each part of your Compose file becomes on a cluster, then edit the example job to run your own services.

In this module

Prerequisites

You have a Docker Compose file and a shared cluster. This page takes you from one to the other in six steps, numbered 0 to 5. You can read it without a cluster account. If “allocation” or “job step” is new to you, take the five-minute tour first.

The method: keep the example job’s orchestration (the allocation, the readiness gate and the cleanup), and replace its placeholder services and its result check with yours. The example job is the baseline workflow in this project’s repository, pinned to a release on GitHub.

Step 0: Is this the right pattern?

This pattern runs your stack once, for a bounded time, inside a single allocation, and then stops. Two questions decide whether that fits.

  1. Does your stack produce results, or is staying up the product?
  2. If one service dies, is restarting the whole stack acceptable?

Results plus an acceptable restart means continue. A stable public endpoint, a service that must outlive a wall-time limit, or a component that has to recover by itself points elsewhere: read when not to use this pattern first.

Step 1: Inventory your Compose file

Make a copy of this worksheet and add one row for each service.

Scroll horizontally to view all table columns.

Service Role Waits for Writes Listens on Needs a GPU?
(name) coordinator, worker, one-shot job, data store or interface the service it depends on, and what proves that service is ready the paths it writes the port it serves yes or no

Then flag anything that has no direct cluster equivalent.

Step 2: Map each construct

For each construct in your Compose file, find its cluster form. The terms job step, readiness gate, job-scoped scratch and loopback are defined in the glossary.

Scroll horizontally to view all table columns.

Compose construct Cluster form Status
docker compose up and down One sbatch submission. The script is the composition. Tested
a service An srun --exclusive job step Tested
deploy.replicas A loop that starts each worker, then waits on each one Tested
healthcheck with depends_on: service_healthy A two-stage readiness gate: wait for the coordinator’s address file, then require its exact answer within a time limit Tested
depends_on: service_completed_successfully Plain sequential flow in the script Tested
a named volume A private job folder with owner-only permissions, bound into the container Tested
ports, for services that talk to each other Loopback on a port the coordinator picks, with its address written to a file Tested
ports, to reach a service from your laptop An SSH tunnel, where your center allows one Designed, not run
build: A prebuilt image, checked against a recorded checksum before use Tested
cleanup on down A cleanup step, registered at the start, that runs on any exit and stops every job step Tested
stopping a stuck service by force, and the scheduler’s time-limit signal A short grace period, then a forced stop Designed, not run
a GPU device request A scheduler GPU request plus the runtime’s GPU flag Designed, not run
the same pattern on PBS-family schedulers or with Charliecloud The same structure with different commands Designed, not run

Tested rows ran in the example job on a cluster. What was tested where names the cluster and links the full test record. Rows marked Designed, not run follow the same pattern, but nobody has run them yet.

Step 3: Fill in the template

Open the example job. To run your own application you change the files below and a small set of settings.

Keep

Replace or edit

Scroll horizontally to view all table columns.

File What to do
slurm/baseline.sbatch Set your account, partition, wall time and CPUs, and swap the coordinator and worker launch lines for your services.
bin/coordinator.py Replace with your coordinating service. The health answer it gives is at lines 27–28.
bin/readiness.py Keep the script and its bounds, but change what “ready” means. The exact answer it waits for is at lines 29–31 and must match what your service returns.
bin/worker.py Replace with your workers.
bin/common.py Edit it if your tasks differ. It checks the example’s task format (an id and a payload with a message and a repeat count), so another shape is rejected as invalid input.
bin/verify.py Replace with your own definition of correct. Checking that every result is right belongs to your application, not to the pattern.
bin/validate_input.py and inputs/ Replace with your inputs and their checksum record.
expected/ Replace or delete. Only the example’s verifier reads it.
container/Apptainer.def and container/image-lock.json Replace with your image and its pinned base.
container/acquire.sh, diagnostics.json and README.md Leave unchanged.

Settings. The job takes 14 adapter variables, set with sbatch --export or a wrapper your center owns. The package README lists the defaults and limits: WORKER_COUNT, TASK_COUNT, READINESS_TIMEOUT, CLEANUP_TIMEOUT, IMAGE_PATH, IMAGE_SHA256_FILE, INPUT_PATH, INPUT_SHA256_FILE, EXPECTED_PATH, BSSW_SCRATCH_ROOT, BSSW_APPTAINER_CMD, BSSW_SRUN_CMD, BSSW_SLURM_VERSION_CMD and BSSW_SHA256_CMD.

Set BSSW_SCRATCH_ROOT explicitly, to a private job-scoped path your center names. Without it the script falls back to the scheduler’s temporary directory and then to /tmp, and on a shared node that fallback is not private. Check that the allocation has enough CPUs for the workers you ask for: the example gives each step one CPU, so raise the CPU request along with WORKER_COUNT.

Step 4: Run it

Run the example walks through checking your center, fetching the image and submitting the job. In short, check the input against its checksum record, then submit with absolute paths. Run both from the root of the repository you cloned in Get the code. The checksum record names a bare tasks.json, so the check runs inside the inputs folder. Slurm runs a copy of the batch script from its own spool folder, so the script cannot find the workflow folder by itself; the --export list gives it absolute paths. Replace <path> with the private scratch path you chose in Step 3.

(cd workflows/baseline-slurm-apptainer/inputs && sha256sum -c tasks.json.sha256)
W="$PWD/workflows/baseline-slurm-apptainer"
sbatch --export=ALL,BSSW_SCRATCH_ROOT=<path>,INPUT_PATH="$W/inputs/tasks.json",EXPECTED_PATH="$W/expected/results.json",IMAGE_PATH="$W/container/baseline.sif",INPUT_SHA256_FILE="$W/inputs/tasks.json.sha256" "$W/slurm/baseline.sbatch"

When the job finishes, it reports that the workflow completed; it does not print a path. The result is in the job’s private folder, <path>/bssw-<job ID>, under the scratch root you set. If you leave BSSW_SCRATCH_ROOT out, the script falls back to ${SLURM_TMPDIR}/bssw-<job ID> when SLURM_TMPDIR exists, otherwise to ${TMPDIR:-/tmp}/bssw-<job ID>, as the package README states.

Step 5: Verify the result

A zero exit code is not success. Success has two parts, both in the job’s private folder.

If either is missing, start with Troubleshooting. Each failure the script reports has a named ID there.

Common mistakes

What varies by center

The structure stays the same. These values change from center to center.

Record your local values before you adapt anything. Run the example lists them.

Key points

Next step

Run the unmodified example first with Run the example, then change one service at a time. To judge whether the pattern fits your center, read Evaluate and adopt.

Complete the decision exercise

Pick one Compose stack you use. Fill in the Step 1 worksheet, write the cluster form of each construct from the Step 2 table, and mark each row Tested or Designed, not run.

Sources and scope

Review source roles and citations
  • SOW commitment: Expand portability patterns across schedulers, runtimes, and workflow shapes. Fellowship SOW, Milestone 2
  • Project decision: Publish a cross-cutting translation guide before releasing scheduler-specific templates. Milestone 2 scope

Project-adopted practices and project decisions are not BSSw Fellowship Program requirements.