Skip to content

Deploy

Container image

Image contents, supported Linux architectures, writable mounts, and container commands.


ysearch ships as a FROM scratch image. There is no shell, no libc, no package manager, no writable home directory, and no implicit /tmp. The image holds the statically linked binaries and a CA bundle, and nothing else.

The root filesystem is read-only, so a deployment mounts every writable path it uses for caches, ingest, or temporary files.

What is in the image

Containerfile builds the data-plane image. It copies the CA certificate bundle out of a Debian stage and puts two binaries into an empty image:

dockerfile
ARG TARGETARCH
FROM docker.io/library/debian:bookworm-slim AS certs
RUN apt-get update \
    && apt-get install -y --no-install-recommends ca-certificates \
    && rm -rf /var/lib/apt/lists/*

FROM scratch
ARG TARGETARCH
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY dist/ysearch-linux-${TARGETARCH} /ysearch
COPY dist/ysearch-operator-linux-${TARGETARCH} /ysearch-operator
USER 65532:65532
ENTRYPOINT ["/ysearch"]
Path Binary Used by
/ysearch The data plane and CLI serve, node, and every client verb; the image entrypoint
/ysearch-operator The Kubernetes operator The operator Deployment, through an explicit command
/etc/ssl/certs/ca-certificates.crt — TLS to an S3 endpoint

One image carries both binaries. The operator Deployment in deploy/operator/operator.yaml overrides the entrypoint:

yaml
containers:
- name: operator
  image: ysearch:latest
  command: ["/ysearch-operator"]
  args: ["--leader-elect=true"]

The admin console image

The admin console is a separate process with its own image. Containerfile.admin holds one binary and nothing else — not even the CA bundle, because the console never dials an object store:

dockerfile
ARG TARGETARCH
FROM scratch
ARG TARGETARCH
COPY dist/ysearch-admin-linux-${TARGETARCH} /ysearch-admin
USER 65532:65532
EXPOSE 8787
ENTRYPOINT ["/ysearch-admin"]

The console reaches an index only through the generated Connect services. It receives no object-store credentials and no Kubernetes service-account token. See the admin console guide for what it can and cannot do.

Both architectures

just build-dual-arch produces statically linked Linux amd64 and arm64 binaries, with receipts, for ysearch, ysearch-operator, and ysearch-admin:

zsh
just build-dual-arch

The Forgejo build-container workflow runs the static checks, cross-compiles both architectures in a pinned builder image, and assembles the scratch layers with crane. It refuses to tag an index unless both the linux/amd64 and linux/arm64 descriptors are present. The image is published to the internal registry as tcr.teixos.net/yannick/ysearch, tagged with the branch name and, on main, latest. There is no public image; a deployment outside that registry builds its own.

Running it

The image runs as USER 65532:65532 with no writable filesystem of its own, so every path ysearch writes to must be mounted. For a single-process server that is the data directory:

zsh
docker run --rm \
  --user 65532:65532 \
  --read-only \
  --tmpfs /tmp \
  -v "$PWD/ys-data:/var/lib/ysearch" \
  -p 9500:9500 -p 9550:9550 \
  ysearch:latest \
  serve --data-dir /var/lib/ysearch --listen :9500

--data-dir (server.data_dir) derives the object, cache, ingest, and model directories beneath one path, so one mount is enough here. A fleet role splits those paths across separate volumes; see the table below.

The mounts a deployment must supply

The operator mounts these paths into every role pod. A hand-written deployment needs the same set.

Mount path Setting that points at it Contents Disposable
/tmp — Scratch Yes
/var/lib/ysearch/cache cache.dir Full-segment and decoded-block caches Yes
/var/lib/ysearch/ingest ingest.dir Spooled batches awaiting a seal No, while a build is open
— object.dir or the object.s3.* keys Authoritative published objects Never

Everything under cache.dir is content-addressed and verified on use. Losing it makes the next request cold; it cannot lose an indexed document. The ingest spool is different: a sealed but unpublished spool is retried, so deleting it loses the documents it held.

Ports

Port Protocol Purpose Setting
9500 gRPC The data-plane API and the gRPC health service server.listen
9550 HTTP Prometheus exposition at /metrics observability.metrics_listen
8787 HTTP The admin console's default listen address (127.0.0.1:8787), in its own image --listen on ysearch-admin

Under the operator the console is started with --listen 0.0.0.0:8080, and its Service publishes port 8080. The image's EXPOSE 8787 reflects the binary's default, not the port a Kubernetes deployment uses.

The gRPC server registers the standard grpc.health.v1.Health service, which the operator's startup, readiness, and liveness probes check. A data-plane process has no HTTP health endpoint.

Development images

More Containerfiles exist, and none of them is for deployment. Containerfile.dev is the Apple Container development image, and Containerfile.ci is the pinned build toolchain the Forgejo workflows run in.

Next