Mindloom uses two GitHub Actions workflows to move code from a feature branch into production.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/No-Country-simulation/G9-LATAM-Team-58/llms.txt
Use this file to discover all available pages before exploring further.
ci.yml validates changes on pull requests and pushes to dev — running only the jobs whose source files actually changed. deploy.yml fires on every push to main, SSH-ing into the OCI Ampere A1 VM to rebuild and restart the containers in place. The build never happens on a GitHub runner: the VM is linux/arm64 and runners are x86, so every image is built natively on the VM.
ci.yml — Validate
Triggers
ci.yml runs on pull requests targeting dev or main, and on direct pushes to dev. It does not trigger on pushes to main — that is deploy.yml’s responsibility.
Path-filtered jobs
The first job,changes, uses dorny/paths-filter@v4 to detect which parts of the codebase were modified. Downstream jobs read its outputs and skip themselves when their paths were not touched:
inference/README.md does not trigger the inference job. A change to docs/ does not trigger any job.
| Job | Trigger paths | What it runs |
|---|---|---|
inference | inference/app/**, inference/tests/**, inference/requirements.txt | pip install torch (CPU wheel), ruff check ., pytest -q |
api | api/pom.xml, api/src/** | ./mvnw -q -B verify (includes contract tests) |
web | web/package.json, web/src/**, web/vite.config.* | pnpm install --frozen-lockfile, pnpm run lint, pnpm run build |
Job details
inference job
inference job
Runs on Python version:
ubuntu-latest. Installs torch from the CPU wheel index first (to avoid pulling 2 GB of CUDA packages), then installs requirements.txt and requirements-dev.txt, runs ruff check . for linting, and pytest -q for tests.3.12 (matches the python:3.12-slim base image in the Dockerfile, required because numba dispatchers pickled inside model.joblib are sensitive to the exact CPython bytecode version).api job
api job
Runs on The contract test mocks the Python inference service and asserts the exact JSON shape of
ubuntu-latest with Java 25 (Temurin distribution, matching <java.version> in pom.xml). Executes the full Maven verify lifecycle, which includes unit tests and the contract test.PredictResponse and EmbedResponse. If someone changes the inference response contract without updating the API client, this test fails in CI before the change reaches production.web job
web job
Runs on
ubuntu-latest. Uses pnpm 11.3.0 with Node 22 (node 22 is required — pnpm 11.x uses node:sqlite built-in, which is unavailable in Node 20). Installs with --frozen-lockfile to mirror the exact dependency tree checked into pnpm-lock.yaml, lints, and builds the static bundle.deploy.yml — Publish
Triggers and concurrency
deploy.yml fires on every push to main and can be triggered manually via workflow_dispatch. The concurrency configuration is critical: cancel-in-progress: false means two pushes to main in quick succession are serialized, not race-conditioned. The second deploy waits for the first to finish, leaving the VM in a consistent state rather than colliding mid-build.
The deploy job
The entire deploy job is a singleappleboy/ssh-action@v1 step. It SSH-es into the VM using three repository secrets and runs the following script:
set -euo pipefail ensures the workflow step fails if any command in the script fails — ssh-action@v1 has no script_stop input, so this is the mechanism that surfaces errors.
The deploy script steps in order:
Fetch and reset
git fetch origin main and git reset --hard origin/main bring the VM’s working tree to the exact state of the main branch tip. Any local modifications inside the ~/techmind directory are discarded. Files outside the git tree (.env, wallet/) are untouched.Build images
docker compose --profile web build rebuilds all three images natively on the ARM64 VM. The --profile web flag includes the web service, which is otherwise hidden behind the compose profile.Restart containers
docker compose --profile web up -d --remove-orphans starts or restarts containers using the freshly built images. --remove-orphans cleans up containers from services that were removed from docker-compose.yml.Prune old images
docker image prune -f removes dangling images (the previous build’s untagged layers) to reclaim disk space on the VM.Why the build runs on the VM
Build on the VM (current approach)
The VM is
linux/arm64 (OCI Ampere A1). Building natively takes minutes, requires no container registry, and produces images that run without any platform translation.Cross-build on the runner (alternative)
Using
docker buildx with QEMU emulation on an x86 runner can produce linux/arm64 images, but takes 20–40 minutes once the transformer weights are baked in. It also requires an intermediate container registry (Docker Hub, GHCR, or OCI Registry) to transfer the image to the VM.Required repository secrets
Configure these three secrets in Settings → Secrets and variables → Actions:| Secret | Description |
|---|---|
VM_HOST | Public IP or hostname of the OCI Ampere A1 VM |
VM_USER | SSH username on the VM (e.g. ubuntu or opc) |
VM_SSH_KEY | Private SSH key (PEM format) authorized on the VM |
OCI_CLI_USER, OCI_CLI_KEY_CONTENT, etc.) are needed. The VM authenticates with OCI Object Storage using Instance Principal — the IAM role is attached to the VM’s compute instance, and the oci Python SDK picks it up automatically.
Branch Rules
- Never push directly to
main. A direct push triggers an immediate production deployment. - All pull requests must pass CI (green
ci.ymlchecks) before merging. - Merges to
mainadditionally require 1 approval. - Rebase your branch on
devbefore opening a PR to keep the history linear.
Troubleshooting
exec format error — container fails to start
exec format error — container fails to start
Cause: An image built on x86 (e.g. built locally on a Mac or Linux x86 machine, or pulled from a registry without the correct platform tag) was deployed to the ARM64 VM.Fix: Ensure the build runs on the VM itself via the deploy workflow, or use
docker buildx build --platform linux/arm64 with an intermediate registry. Never build on a GitHub runner and copy the image binary to the VM without specifying the target platform.API returns 503 on all endpoints
API returns 503 on all endpoints
Cause: The
db Spring profile is not active, or the Oracle wallet is not mounted correctly.Check:- Verify
SPRING_PROFILES_ACTIVE=dbis set in the container —docker compose exec api env | grep SPRING_PROFILES. - Verify the wallet is mounted —
docker compose exec api ls /app/walletshould listtnsnames.oraand other wallet files. - Check API startup logs —
docker compose logs api— forDriverManagerorTNSerrors.
inference stays unhealthy — API never starts
inference stays unhealthy — API never starts
Cause: The
load() function failed during startup. This happens when models/latest.txt does not exist in the OCI bucket, or when the path it contains does not point to a valid model.joblib.Check:docker compose logs inference— look for the exception fromload().- Verify
models/latest.txtexists in theMODEL_BUCKETbucket and contains a valid path prefix. - If the healthcheck window is too short for your model size, increase
--start-periodin the inferenceDockerfile.
web job fails with --frozen-lockfile
web job fails with --frozen-lockfile
Cause:
pnpm-lock.yaml is out of date or missing. The CI job uses --frozen-lockfile, which aborts if the lockfile does not match package.json.Fix: Run pnpm install locally to regenerate the lockfile, then commit and push the updated pnpm-lock.yaml.Manual VM changes disappeared after deploy
Manual VM changes disappeared after deploy
Cause: The deploy script runs
git reset --hard origin/main, which overwrites any modifications inside the ~/techmind git working tree with the contents of main.Fix: Never edit files inside ~/techmind that are tracked by git. Configuration that must persist across deploys (.env, wallet/) lives outside the git tree and is untouched by git reset --hard.Deploy appears stuck — two pushes in flight
Deploy appears stuck — two pushes in flight
Cause: Two merges to
main happened in close succession. The concurrency: cancel-in-progress: false setting serializes deploys rather than cancelling the first one.Action: Check the GitHub Actions tab — the second run will be queued with status Waiting. It will start automatically once the first completes. Do not cancel or re-trigger manually.