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.
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
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 codexOr for the rust image:
DEVA_DOCKER_IMAGE=deva-local \
DEVA_DOCKER_TAG=rust \
deva.sh claudeDeva checks local images first. If the image is already there, it does not need to pull anything.
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:dailyThen point deva at it:
export DEVA_DOCKER_IMAGE=ghcr.io/yourname/deva
export DEVA_DOCKER_TAG=daily
deva.sh codexThat is the whole trick. This is not Kubernetes. It is just an image name plus a tag.
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.
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 geminiThat is usually the sane move.
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.
You do not need to replace your whole system install.
Per-shell:
DEVA_DOCKER_IMAGE=deva-local \
DEVA_DOCKER_TAG=latest \
deva.sh codexPer-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.
- 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.
Use this before blaming the wrapper:
DEVA_DOCKER_IMAGE=deva-local \
DEVA_DOCKER_TAG=latest \
deva.sh codex --debug --dry-runIf the printed image is wrong, your override is wrong. If the printed image is right, the problem is somewhere else.