Custom Images¶
This is the guide for people who want their own deva image instead of the stock one.
Common reasons:
- you want extra tools baked in
- you want a personal image in your own registry
- you want local experiments without waiting for upstream releases
That is fine. deva does not care where the image came from. It cares that the image exists and that the tag you asked for is real.
The Two Knobs¶
Deva picks the runtime image from two host-side variables:
DEVA_DOCKER_IMAGEDEVA_DOCKER_TAG
If you do nothing, it uses:
DEVA_DOCKER_IMAGE=ghcr.io/thevibeworks/deva
DEVA_DOCKER_TAG=latest
For the rust profile, the default tag becomes rust.
Important detail:
- if you set only
DEVA_DOCKER_IMAGE,PROFILE=rustcan still change the tag torust - if you want zero surprises, set both
DEVA_DOCKER_IMAGEandDEVA_DOCKER_TAG
Build A Local Image¶
Base image:
docker build -t deva-local:latest .
Rust profile image:
docker build -f Dockerfile.rust -t deva-local:rust .
Then run deva against it:
DEVA_DOCKER_IMAGE=deva-local \
DEVA_DOCKER_TAG=latest \
deva.sh codex
Or for the rust image:
DEVA_DOCKER_IMAGE=deva-local \
DEVA_DOCKER_TAG=rust \
deva.sh claude
Deva checks local images first. If the image is already there, it does not need to pull anything.
Build A Personal Registry Image¶
If you want your own registry namespace, tag it that way from the start:
docker build -t ghcr.io/yourname/deva:daily .
docker push ghcr.io/yourname/deva:daily
Then point deva at it:
export DEVA_DOCKER_IMAGE=ghcr.io/yourname/deva
export DEVA_DOCKER_TAG=daily
deva.sh codex
That is the whole trick. This is not Kubernetes. It is just an image name plus a tag.
Keep It Personal¶
If this is only for you, put the override in .deva.local:
DEVA_DOCKER_IMAGE=ghcr.io/yourname/deva
DEVA_DOCKER_TAG=daily
That file is the right place for personal registry tags, private images, and "I am trying weird stuff" experiments.
If the whole team should use the same custom image, put it in .deva
instead:
DEVA_DOCKER_IMAGE=ghcr.io/acme/deva
DEVA_DOCKER_TAG=team-rust-20260312
Yes, deva's config loader will export those variables for the wrapper. That is intentional.
Extend The Official Image Instead Of Starting From Nothing¶
If you just need a few extra tools, do not rebuild the universe.
Use the published image as your base:
FROM ghcr.io/thevibeworks/deva:latest
RUN apt-get update && apt-get install -y --no-install-recommends \
graphviz \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
Build and run it:
docker build -t deva-local:extras .
DEVA_DOCKER_IMAGE=deva-local DEVA_DOCKER_TAG=extras deva.sh gemini
That is usually the sane move.
What Still Comes From The Wrapper¶
Changing the image does not change the wrapper model.
Deva still controls:
- workspace mounts
- auth mounts
- config-home wiring
- container naming
- persistent vs ephemeral behavior
- debug and dry-run output
So a custom image is not a custom launcher. It is just a different root filesystem under the same wrapper behavior.
Personal Use Without Installing Anything Globally¶
You do not need to replace your whole system install.
Per-shell:
DEVA_DOCKER_IMAGE=deva-local \
DEVA_DOCKER_TAG=latest \
deva.sh codex
Per-project:
# .deva.local
DEVA_DOCKER_IMAGE=deva-local
DEVA_DOCKER_TAG=latest
That way your personal image only affects the project where you meant to use it. Amazing concept.
Gotchas¶
- If you set only the image and forget the tag, profile defaults may
still pick
latestorrust. - If the image is private, pulls need auth. Public docs pointing at a private image are broken by definition.
- If you build a custom image that removes expected tools or paths, deva will not magically repair your bad Dockerfile.
- If your image tag does not exist locally and cannot be pulled, deva fails fast. Good. Silent nonsense would be worse.
Sanity Check¶
Use this before blaming the wrapper:
DEVA_DOCKER_IMAGE=deva-local \
DEVA_DOCKER_TAG=latest \
deva.sh codex --debug --dry-run
If the printed image is wrong, your override is wrong. If the printed image is right, the problem is somewhere else.