Private Docker Registries
Boot VMs from private images with --registry-auth, or pull private images inside a running VM with a registry integration.
Private images come up at two different moments, and they have different
answers:
- **Booting a VM from a private image** (`new --image=...`): the boot image
is pulled by the exe.dev host *before your VM exists*. Pass
[`--registry-auth`](#booting-from-a-private-image).
- **Pulling private images inside a running VM** (`docker pull`, CI jobs,
agents): attach a
[container registry integration](#pulling-private-images-inside-your-vm) —
no `docker login`, no credential on the VM.
## Booting from a private image
If the image you give to the [`new` command](/docs/cli-new) lives in a
private registry (ghcr.io, Docker Hub, GitLab, ECR, ...), pass
`--registry-auth=USERNAME:PASSWORD`:
```
new --image=ghcr.io/OWNER/IMAGE:TAG \
--registry-auth=USERNAME:TOKEN
```
The boot image is pulled host-side, before your VM exists — so the
integration hostnames described below (`<name>.int.exe.xyz`) are not
available to this pull; don't use one as the registry host in `--image`.
Provide the registry credential with `--registry-auth`.
**ghcr.io** requires a [classic Personal Access
Token](https://github.com/settings/tokens/new?scopes=read:packages&description=exe.dev%20ghcr%20pull)
with `read:packages`.
**Docker Hub** accepts a [personal access
token](https://app.docker.com/settings/personal-access-tokens/create)
with read scope.
## Pulling private images inside your VM
Once a VM is running, a
[container registry integration](/docs/integrations-oci-registries) lets it
pull private images without `docker login` and without placing a registry
credential on the VM. Attach a `ghcr` or `quay` integration from the
[catalog](/docs/integrations-catalog), then use the integration hostname as
the registry:
```
docker pull <name>.int.exe.xyz/OWNER/IMAGE:TAG
```
exe.dev holds the registry credential and presents it only at the
registry's own token endpoint; stock `docker`, `podman`, `skopeo`, and
`crane` work unmodified. See
[Container Registry Integrations](/docs/integrations-oci-registries) for
the full picture, and
[attaching integrations](/docs/integrations-attach) for how attachment
works.
## Run a registry on an exe.dev VM
By default, the [`new` command](/docs/cli-new) assumes that the image you
give it is stored in a public Docker repository. As an alternative, you can run
a Docker registry on exe.dev, and use that registry to store other images
for your VMs.
First, create a VM:
```
$ ssh exe.dev new --name private-registry-test
```
Then, run a Docker container that runs the Docker registry on it:
```
$ ssh private-registry-test.exe.xyz docker run -d --name registry -p 8000:5000 registry:2
```
Then, on the registry machine (or elsewhere), build the image:
```
$ cat > Dockerfile <<EOF
FROM alpine:latest
RUN echo exe.dev > /hello.txt
EOF
$ docker build -t localhost:8000/my-image:v1 .
$ docker push localhost:8000/my-image:v1
```
Finally, create a new VM, <b>using the VM hostname as the registry host</b>.
```
$ ssh exe.dev new --image private-registry-test.exe.xyz/my-image:v1
Creating oboe-hydra using image my-image:v1...
```
And observe it working:
```
$ ssh oboe-hydra.exe.xyz cat /hello.txt
exe.dev
```