Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

#185

Let a job start service containers beside it

merged · by keenan 5d ago · merged into main at c1929d2f by keenan 5d ago

keenan opened this 5d ago

A test that needs a database, a queue, or another server has had to bring it into the job image, along with its client and a startup script. A workflow could not use the upstream postgres image as it is. This adds services: to a job, so the runner starts those containers beside the job and a step reaches them by name.

jobs:
  test:
    image: golang:1.27-bookworm
    network: bridge
    services:
      db:
        image: postgres:17
        env:
          POSTGRES_PASSWORD: test
          PGDATA: /tmp/pgdata
    steps:
      - run: DATABASE_URL=postgres://postgres:test@db:5432/postgres go test ./...

What it does:

  • Schema. services is a named mapping on the job. The service name is a DNS label and the hostname a step uses. A service has an image and literal env only; it is not a job, has no checkout, secrets, or host mount, and at most three run beside one job. A registry reference is required; image-file: is refused.
  • Network. A job with services must declare network: bridge. The runner creates one user-defined network for the attempt, starts the services with --network-alias, and starts the job on the same network. The job and its services are the only members, so names resolve and no host port is published. Without bridge, the push is refused; a service must not quietly give a job a network it did not ask for.
  • Lifecycle. Services start after the job image is resolved and before the first step. A service that exits before the steps fails the job with its output. The job stops before artifacts are read; services and the network are removed on success, failure, timeout, and cancel. A killed runner’s containers and networks are found by label on its next start.
  • Logs. On a failed, timed-out, or canceled attempt, the last 256 KiB of each service’s output goes into the attempt log under === service db ===. A passing run drops it. The runtime’s stdout and stderr share a locked tail buffer, so the two streams cannot corrupt it.
  • Pinning and display. Services are pinned in the run row and lease like the steps and environment, and the run page names them beside the image.
  • Local run. vole workflow run starts the same services on the same kind of private network and refuses the same combination. Its fake-runtime tests cover start order, early exit, service logs, and cleanup.

Decisions worth reviewing:

  • The job’s uid. A service runs as the job’s user, with the same capabilities, user namespace, and resource limits. A service image that insists on root is not supported; the postgres example points PGDATA at /tmp and works as an unprivileged user. This keeps one container from being more privileged than the other.
  • No secrets. The job’s secret file is a mount in the job container, and a service has no job tree. A test credential is a literal in the workflow; real secrets stay with the job.
  • No health check. A step that needs a server to accept connections waits for it, as pg_isready does. This slice does not add a readiness command.
  • Resource flags are per container. --container-memory, --container-cpus, and --container-pids apply to each service too; the three-service cap is the bound. The docs say so.

Checks: vole workflow check and vole workflow run verify pass, including the race test suite. I also ran the local command against a real runtime with nginxinc/nginx-unprivileged:alpine and with postgres:17-alpine using the example above; the step reached both by name, and no containers or networks were left behind.

Closes #184.

Discussion (0)

No discussion yet.