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.
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:
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:
containers:
- name: operator
image: ysearch:latest
command: ["/ysearch-operator"]
args: ["--leader-elect=true"]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:
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.
just build-dual-arch produces statically linked Linux amd64 and arm64
binaries, with receipts, for ysearch, ysearch-operator, and
ysearch-admin:
just build-dual-archThe 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.
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:
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 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.
| 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.
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.
- Kubernetes — what the operator renders from these images
- Object store — the credentials the container needs
- Configuration reference — the cache budgets and their defaults