Root Loco Linux images¶
Build the root application with task image:build, then run task image:test.
Both tasks accept LOCO_IMAGE (default medtracker-loco:local). Docker is required.
The build uses the committed Cargo and npm lockfiles and accepts CARGO_BUILD_JOBS
(default 2, use 1 on a constrained VM). The checks create and remove
their own PostgreSQL container, migrate its empty database, and boot the server
and worker with a restricted runtime login. No existing database is used.
The final scratch image contains the Linux executable, its shared libraries,
TLS roots, timezone data,
production configuration, Tera templates, static assets and PDF fonts. It runs as
UID/GID 65532, without a shell or compiler.
Native scheduler execution is covered separately by
task test:scheduler:process and task test:scheduler:image LOCO_IMAGE=<image>.
The image acceptance creates a private database and restricted login, expires a
support session, then inserts another session and waits for a later cron tick.
It checks both attributable audit records, absence of duplicate expiry, UID 65532,
missing /bin/sh and clean scheduler shutdown. These checks execute a schedule;
listing configured jobs alone does not prove execution.
On a Docker host with exhausted default network pools, the image checks accept
LOCO_IMAGE_SUBNET=<unused-private-subnet>. Choose an unused subnet; the checks
remove only the network and containers they created.
Loco's released 1.2.0 scheduler required a correction for direct native-task
execution. The owner approved a temporary exception to the registry-only dependency
rule and patched loco-rs to
damacus/loco commit 5224bc58f9292a2f4713ee333bded785e5266f09.
The 1.3.0 release includes shell-free scheduling and is used from the registry.
The application checks native scheduler commands for NUL bytes before starting.
The fork starts at published 1.2.0's recorded source commit
43b3fe60db487758c195d6947f1c37e5246cedf2,
from the release archive's .cargo_vcs_info.json. That archive's SHA-256 is
5386baf405736a2620001f4561b00fa4874dae99eac3ccf01713fabc9ef13af4.
The fork diff
contains the scheduler fix, its tests and exact shlex dependency. Its loco-gen
dependency was the published 1.2.0 package. Upstream PR #1887
proposed the corresponding fix there.
Configured native jobs parse grouping and escaping with shlex, then execute
the application directly. Shell operators and expansions remain literal arguments.
Empty task names, malformed quotes and NUL bytes fail before spawning. Explicit
shell: true jobs still need a platform shell, which the scratch image lacks.
Cron timing, filters, environment inheritance, output modes and shutdown remain
owned by the released framework.
The fork's scheduler tests cover exact native arguments, executable paths with
spaces, separate startup and periodic callbacks, output modes, nonzero exits,
missing executables and failure-log privacy. The application retains its own
process and image acceptance checks when updating the framework.
Native run values support quoted arguments but do not expand shell syntax.
shell: true explicitly requests a platform shell and cannot run in this scratch
image. Keep production schedules as native tasks.
The Loco Linux images workflow builds and checks amd64 and arm64 on native
runners. Every push to main publishes a multi-platform
ghcr.io/damacus/med-tracker-loco:sha-<full commit> image and can update beta
after the native image checks, scheduler acceptance and image-transfer checks pass.
Dispatching from main with publish enabled also publishes; other
dispatches only check the images. Publication verifies checksums, image IDs and architectures of the
saved tested images, pushes those images, and joins their registry digests into
the multi-platform manifest. The publication job has no source checkout, build
or application execution. Pull requests and dispatches from other branches have
no package write permission and cannot publish. Transferred image archives
expire after one day.
The release-please workflow also calls this workflow after creating a release.
It checks out the exact release tag and verifies that the tag's commit matches
the workflow commit and its version matches the root Cargo package. It builds
and checks fresh images from that source, then publishes the tested images with
both their SHA tag and the full release version, for example
ghcr.io/damacus/med-tracker-loco:0.6.0-rc.1. No latest, minor or major alias is
published. The workflow summary records the verified multi-platform digest for
the deployment controller to use. A version tag alone does not prove the image
has finished building; wait for the publication job to succeed.
PR #3028 removed the Rails application and its separate image publisher.
Publishing an image does not deploy it. Builds run independently for each main
push, so a queued push is not displaced by the next commit. Publication uses a separate concurrency group
per full source SHA, with cancellation disabled. Main and release-please builds
of the same commit cannot overwrite the SHA tags while the other publisher is
recovering their digests. Different commits can publish independently. A separate Actions
concurrency group serializes mutable beta promotion; only its latest pending
promotion needs to run. Each promotion checks the current main tip through the
GitHub API and skips older revisions. A failed or malformed ref lookup fails
without changing beta. Rerunning an older commit can publish its SHA image but
cannot move beta backwards. Promotion copies the published immutable manifest
and verifies the resulting digest. The run summary records the source commit,
immutable digest and whether beta was promoted or skipped. Pin that digest in GitOps. A build
after merging a pipeline change uses that subsequent main commit, rather than
the main commit observed before the change.
Creating the 0.6 release candidate¶
Release-please uses the root Rust package.
The one-time release-as: 0.6.0-rc.1 setting selects the first candidate and
prerelease: true marks the GitHub release as a prerelease. The manifest records
the last released version; do not advance it manually to request a release.
- Merge the release configuration and workflow change through the normal review
process. Release-please updates its pending release PR to
0.6.0-rc.1, including the Cargo files, changelog and manifest. - Review and merge that generated release PR. Release-please creates
v0.6.0-rc.1, then calls the checked Rust image publisher. Both architectures, the production boot checks and native scheduler acceptance must pass before publication can run. - Record the image digest from the successful publication summary. Use that digest for server, worker, scheduler and schema setup through the deployment's existing GitOps controller. Follow the fresh rollout notes for the separate database, credentials and runtime identity.
- Remove or advance the one-time
release-asoverride before the next release. For another candidate, set the explicit version, such as0.6.0-rc.2, while retainingprerelease: true. For the stable release, select0.6.0and setprerelease: false. Do not reuse a candidate tag for changed source.
The image workflow builds and publishes images only. It does not provision a database or accounts, export or import clinical records, or change traffic. A 0.6 installation uses a new empty database and fresh accounts. Use the supported portable-data contract when import or export is needed; its Rails compatibility limits remain documented in #2702. This issue does not define a cutover plan or gate image publication.
task image:check compares the image's pinned Rust compiler with the compiler
selected by the application's stable CI policy, its Node version with mise.toml
and the exact Node patch selected by app CI, and its Task version with app CI.
The local check compares Node majors; CI additionally compares the resolved patch.
The image uses Rust 1.99.1 and Node 26.12.1;
Cargo.toml's rust-version remains the minimum supported compiler. A new stable
release makes the alignment check fail until the Rust image version and verified
digest are updated. Node 26 and Task 3.52.0 match the application's selections.
The build frontend, base images, BuildKit daemon and workflow actions are pinned
by digest or commit. CI verifies Task 3.52.0 and Buildx 0.38.1 binaries against
committed checksums before running them. BuildKit 0.34.1 runs without the default
host-network and insecure-security entitlements.
Cargo and npm use their lockfiles; npm dependency install scripts are disabled.
Build caches are separated by architecture and contain no deployment credentials.
The smoke checks use a read-only filesystem with all capabilities dropped and
privilege escalation disabled. They pin their disposable PostgreSQL image too.
Use the same image with these commands (arguments follow the image name):
| Process | Arguments | Database identity |
|---|---|---|
| Server (default) | start --environment production |
Restricted runtime role |
| Worker | start --worker --environment production |
Restricted runtime role |
| Scheduler (one instance) | scheduler --environment production |
Restricted runtime role |
| Schema setup (separate job) | db migrate --environment production |
Migration role |
The server binds to 0.0.0.0:5150. Supply DATABASE_URL,
MEDTRACKER_PUBLIC_HOST, AUTH_SESSION_SECRET and MEDTRACKER_SESSION_KEY through
the deployment's secret/configuration mechanism. All other production settings
remain in config/production.yaml. Schema migration is disabled at application
startup. Follow the fresh rollout guidance for the
fresh database and credential boundary.
Workers and servers need the same configured private S3 storage. Credentials and
uploaded files are never built into the image. With a read-only root filesystem,
mount a writable temporary directory at /tmp for UID 65532. No local persistent
volume is needed for configured object storage. Mount an alternate production
configuration at /app/config/production.yaml if the deployment needs settings
not exposed by the packaged configuration. Run a single scheduler independently
of worker replicas; do not also enable --scheduler on those workers.
Configure the container platform to probe the server's HTTP /health endpoint
on port 5150 and supervise the worker and scheduler processes separately. The
scratch image has no embedded Docker HEALTHCHECK: executing an HTTP probe
inside it would require adding another executable. Trivy reports this as a low
severity configuration finding; platform probes provide the health check.