Running it in Docker
A tool that asks you to trust it with confidential documents should be easy to inspect and easy to try. The image is one statically linked binary on distroless/static, about 19 MB. It has no shell, no package manager and no interpreter. So there is very little to read, and very little to go wrong.
The default build does not depend on an HTTP client at all, since that sits behind the ner feature. So the image has no capacity to make a network request, whatever it is asked to do.
docker pull ghcr.io/mcanouil/oboro:latestA second variant, the ner image, trades that minimalism for name recognition with the model already inside.
The vault has to outlive the container
A container’s filesystem disappears when the container exits. The mapping between placeholders and real values lives in that filesystem.
Without -v oboro-vault:/vault, clean will happily produce a sanitised document whose values are gone the moment the command finishes. restore then leaves every placeholder untouched, and warns that it does not recognise them. That is the last chance you get to notice.
Create the volume once:
docker volume create oboro-vaultThen mount it on every run. The image sets OBORO_VAULT and OBORO_KEY_FILE to point inside it. So no command has to repeat the paths.
oboro uninstall is a host command. It cannot see, and does not touch, anything a container run left behind. Remove the volume yourself, and the image too if you pulled one:
docker volume rm oboro-vault
docker rmi ghcr.io/mcanouil/oboro:latest ghcr.io/mcanouil/oboro:nerCleaning a document
docker run --rm \
-v oboro-vault:/vault \
-v "$PWD":/work -w /work \
--user "$(id -u):$(id -g)" \
ghcr.io/mcanouil/oboro:latest clean contract.docx--user matters. Without it, the container writes as its own unprivileged user, and contract.clean.md arrives on your disk owned by someone else.
Restoring an answer
The same volume, so the mapping is there:
docker run --rm \
-v oboro-vault:/vault \
-v "$PWD":/work -w /work \
--user "$(id -u):$(id -g)" \
ghcr.io/mcanouil/oboro:latest restore answer.mdReviewing
review is a terminal interface, so it needs a terminal attached:
docker run --rm -it \
-v oboro-vault:/vault \
-v "$PWD":/work -w /work \
--user "$(id -u):$(id -g)" \
ghcr.io/mcanouil/oboro:latest review contract.docxWorth a shell function
The flags are the same every time. That is exactly what a shell function is for:
oboro() {
docker run --rm -it \
-v oboro-vault:/vault \
-v "$PWD":/work -w /work \
--user "$(id -u):$(id -g)" \
ghcr.io/mcanouil/oboro:latest "$@"
}After that, oboro clean contract.docx behaves as though it were installed.
What the default image cannot do
Optical character recognition needs the Tesseract system libraries. The recognition model links ONNX Runtime, which has no musl build, so it cannot sit in this static image either; the model also needs a 348 MB download. Putting either capability in this image would cost every user its weight, even those who will not enable it. So neither is included.
The default image reads .txt, .md, .csv, .tsv, .docx, .eml, .odt, .pptx, .xlsx and text-based .pdf. It finds structured values, along with anything on your denylist. For names it has not been told about, use the ner image below, or a binary built with --features ner as described in the quickstart.
Run doctor to see what any given build can do:
docker run --rm -v oboro-vault:/vault ghcr.io/mcanouil/oboro:latest doctorThe ner image
The ner tags carry the same tool built with --features ner, with the recognition model already inside the image. Oboro hash-verifies the model when it builds the image:
docker pull ghcr.io/mcanouil/oboro:nerEvery command works exactly as above. Only the tag changes. This image finds names and organisations nobody listed, out of the box, and downloads nothing on first use.
The trade-offs invert. This image is glibc on distroless/cc, rather than static musl. It weighs about 450 MB, rather than 19. The binary carries a TLS stack, because the ner feature depends on an HTTP client for models pull. The container still has no shell, no package manager and no interpreter. The baked-in model means it never needs to reach the network: run it with --network none to make that a guarantee, rather than a promise.
Pinned tags exist alongside the floating one: each release is also published as <version>-ner, and the tip of main as main-ner.
Building it yourself
Both Dockerfiles are in the repository root and are short enough to read in a minute:
docker build -t oboro . # default, static musl
docker build -f Dockerfile.ner -t oboro:ner . # ner, model baked inocr cannot be added on top of either: it needs the Tesseract shared libraries at run time, not only while building.
The supported way to get an ocr build in a container is the devcontainer, which already carries the toolchain, Tesseract and the OCR libraries. If you want a standalone image with every feature, build your own on a glibc base, rather than extending the shipped ones:
FROM rust:1-bookworm AS build
RUN apt-get update && apt-get install -y --no-install-recommends \
libtesseract-dev libleptonica-dev pkg-config clang
WORKDIR /src
COPY . .
RUN cargo build --release --features "ner,ocr"
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
tesseract-ocr tesseract-ocr-eng \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /src/target/release/oboro /usr/local/bin/oboro
ENTRYPOINT ["oboro"]The runtime stage keeps tesseract-ocr, since OCR loads it at run time, plus one tesseract-ocr-<language> package per language your documents are in. A ner build made this way still needs oboro models pull on first use. This is an advanced, unsupported path. The published images stay the default and ner builds, for the reasons above.