- Dockerfile 76.2%
- Makefile 23.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
Build & publish the CI image / build (push) Successful in 8s
Forgejo's per-job token authenticates ("Login Succeeded") but is not
allowed to write packages: the push died on 401 at the blob upload
(forgejo#1296, #7352). Use REGISTRY_USER / REGISTRY_TOKEN repo secrets
instead; the comment records why so nobody retries the job token.
The rest of the first run was green — checkout, docker daemon reachable
from the job, and the full image build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
| .forgejo/workflows | ||
| Dockerfile | ||
| Makefile | ||
| README.md | ||
CI image — git.botyglot.tech/botyglot-public/ci-ruby
Custom, slim CI image for the self-hosted Semaphore CI (oscar-00016). Purpose: job pods run with zero runtime installs — the Semaphore toolbox, Erlang, a pinned Node and Chrome are all baked in.
What's inside
- Ruby 3.4.9 (base
ruby:3.4.9-slim-bookworm) - Node 22.12.0 (pinned —
NODE_VERSIONbuild arg) + yarn 1.22.x - Google Chrome stable (capybara/selenium system specs; chromedriver is fetched at runtime by Selenium Manager, which needs network)
- Semaphore self-hosted toolbox (
checkout/cache/artifact/test-results/when/sem-context/retry/spc) in/usr/local/bin - Erlang (
erl) so the toolboxwhenCLI installs/runs - build-essential, pkg-config, libpq-dev, libffi-dev (fiddle, via
sidekiq-ent→einhorn on forge), postgresql-client, git, git-lfs (repos use
LFS, e.g.
db/specifications), unzip (semaphore/forge/checkout.shunpacksdb/specifications/current.zip), openssh-client, ffmpeg, sudo - non-root user
semaphore(uid 1000, passwordless sudo), home/home/semaphore— named like on Semaphore's hosted VMs so hosted-era secrets that inject files to absolute/home/semaphore/...paths keep working in job pods - GitHub host keys baked into
/etc/ssh/ssh_known_hosts(hosted VMs pre-seed them; without this, SSH clones hang on the host-key prompt in a bare container)
Build & push
The image must be linux/amd64 (Hetzner CPX nodes).
Automatic — Forgejo Actions (.forgejo/workflows/build.yml): every push to
main touching the Dockerfile, the Makefile or the workflow rebuilds the
image on our own runner and publishes it. Authentication uses the per-job token
Forgejo injects — no PAT to create or rotate.
Manual fallback (from a workstation; Apple Silicon uses buildx emulation):
docker login git.botyglot.tech -u <user> # PAT with package write
make release
⚠️ Bump
BUILDin the Makefile on every image change. Pinned tags are pulledIfNotPresenton the agent node, so republishing an existing tag silently serves the old image.
No Docker on the agent k3s node (it uses containerd), so the image is built by CI/a workstation, not on the cluster.
Use it in a pipeline
In the project's .semaphore/semaphore.yml:
agent:
machine:
type: s1-botykube
containers:
- name: main
image: git.botyglot.tech/botyglot-public/ci-ruby:3.4.9-5
- name: db
image: pgvector/pgvector:pg17
env_vars: [{ name: POSTGRES_HOST_AUTH_METHOD, value: trust }]
- name: redis
image: redis:7
The package owner Botyglot-public is a public organisation, so the image
is pullable anonymously: no image-pull secret to configure on the cluster, and
nothing to rotate.
Scope — what belongs in here
Only what every job needs. A tool used by a single block stays a per-run
install in that block's commands:.
Baking a per-block tool would pin its version to the image's rebuild cadence, and
for linters that's backwards: brakeman and bundler-audit are wanted at their
latest on every run — that's the point of running them. They were briefly baked
(f263fca) and reverted for exactly this reason. Same test for anything else: if
one block needs it, it does not go in the image.
What it lets us drop (once in use)
botyglot-devopsinitial_setup.sh(container branch): the runtime toolbox + erlang install.semaphore.ymlafter_pipeline: the toolbox-install prologue.- Node-version drift (cimg shipped Node 24; here it's pinned to 22.12.0).
Known gaps (deliberate, see ticket oscar-00016)
- Fonts: only dejavu + liberation (22 faces) vs. a much richer set in
cimg/ruby:*-browsers→ the first Percy run will show baseline diffs. Decide (addfonts-noto-core/fonts-noto-color-emoji, or re-baseline as-is) before that run; later font changes invalidate baselines again. postgresql-clientis 15 (bookworm) while the DB sidecar is PG 17.psqlis fine cross-version;pg_dump/pg_restorerefuse to target a newer server. Not on the critical path today (schema_formatis:ruby, apartmentuse_sql = false).- No
libvips/ImageMagick —image_processingis a dependency of sentinel (~1.2) and chady-erp (~2.0), and Rails 7+ defaults to the:vipsvariant processor. Not exercised by sentinel (no attachments); addlibvips42when migrating chady. puppeteer-ruby(chady) downloads its own Chromium unless pointed at the baked one viaPUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome.- Do NOT add
sem-serviceorsem-version:initial_setup.shdetects container mode withcommand -v sem-service, so shipping either flips the detection and re-enables the VM-only code paths.
Maintenance
- Bump Ruby → change the
FROMtag +RUBY_VERSION. Bump Node →NODE_VERSION. - Keep in sync with each project's
RUBY_VERSION/NODE_VERSIONenv. - Chrome is
stableat build time; rebuild periodically for security updates. - ⚠️ Republishing the same tag does NOT reach the cluster. Pinned tags are pulled
IfNotPresent, so the agent node keeps serving its cached copy — jobs silently run the old image. Either bump the tag (:3.4.9-2), or purge it on the node:
(sudo k3s crictl rmi git.botyglot.tech/botyglot-public/ci-ruby:3.4.9-5:latestis pulledAlways— handy while iterating on the image, repin afterwards.)