DocsInfrastructure
Registry
The local registry where built images are stored (a deploy prerequisite), external registries, and disk maintenance.
- Local registry
- Required — where built images live
- External registry
- Credentials for other registries
- Maintenance
- Per-application retention + nightly cleanup
The registry's role in a deploy
- 01buildthe image is built by the Docker daemon
- 02push → localhost:5000/<project>/<app>:<id>
- 03runa container is created from that image
- 04rollbackthe old image is pulled back from the registry
Every deploy pushes an image to the local registry, and a rollback pulls it back from there. That's why the registry must be provisioned before the first deploy — without it, deploys fail with No self-hosted registry. Applications on a remote server don't use the registry (the image stays on that server's daemon).
Local registry
Registry menu → Local registry tab → Provision registry (owner)
aoox runs
registry:3as theaoox-registrycontainer with htpasswd authentication, data on theaoox_registry_datavolume, and tag deletion enabled. The card shows a Registry ready status, the container, and the URL.Before provisioning, pick where images are stored: Local (VPS disk, the default) or one of the S3 destinations already added under Instance backup. It can't be changed afterward without removing and re-provisioning.
Save the credentials
The
aooxusername and a random password are shown when the registry is enabled — click I've saved it after writing it down. Deploying from the dashboard does not need it; these credentials are for pushing manually from another machine. If you lose it, owners and admins can show it again, and owners can reset it without losing any image.
docker login localhost:5000 -u aoox
docker tag myimage:latest localhost:5000/tools/myimage:1.0
docker push localhost:5000/tools/myimage:1.0| Env (.env.dist) | Default | Notes |
|---|---|---|
REGISTRY_PORT | 5000 | The registry's host port. |
REGISTRY_PUBLISH_ADDRESS | empty (127.0.0.1) | Which network interface the host port listens on. The default keeps the registry reachable from this server only; set 0.0.0.0 to let other machines push by IP (see below). |
REGISTRY_PUBLIC_HOST | localhost | The host used by docker push when there's no custom domain (see below). localhost is exempt from Docker's insecure-registry rule; any other host needs TLS (a reverse proxy) or an insecure-registries entry in daemon.json on every daemon that pushes to it. |
Who can reach port 5000
From 0.1.0-alpha.13 the registry's host port is bound to 127.0.0.1 by default, so only this server can push to localhost:5000 (deploys, the host's own docker push, and the panel itself are unaffected: the API reaches the registry over the internal aoox network). Before, the port listened on every interface, which exposed a password-protected registry to the internet over plain HTTP. The card says this server only next to the address while that is the case. To push from another machine, pick one:
- Give it a custom domain (recommended, below): HTTPS through the proxy, nothing else to open.
- Expose the port by IP: set
REGISTRY_PUBLISH_ADDRESS=0.0.0.0in.env.dist, restart the stack (docker compose ... up -d), then recreate the registry container (re-enable it, or apply/remove a custom domain). The card then shows the address other machines use (REGISTRY_PUBLIC_HOSTor this server's public IP). It is plain HTTP, so those machines need the address ininsecure-registries, and password protection is all that stands between the registry and the internet — prefer the domain.
IP:5000 from a laptop or CI, that stops working afterwards unless you expose the port as above or switch to a domain.Custom domain
An alternative to manually juggling REGISTRY_PUBLIC_HOST and insecure-registries: route the registry through the built-in reverse proxy (Traefik) with a real Let's Encrypt certificate — docker push/login trust it out of the box, no extra config needed on any daemon, including other Swarm nodes.
- Needs the proxy already provisioned (see Proxy & domain) with
PROXY_ACME_EMAILset. - Fill in the Custom domain field on the Local registry card (owner/admin), then Apply — the container is recreated with Traefik labels (router
aoox-registry), data and credentials are untouched. Or from the CLI:
aoox registry domain --set registry.example.comOnce the domain is active, docker login registry.example.com (no port — standard HTTPS). The host port stays bound as configured above (loopback by default). Remove the domain with aoox registry domain --clear or the Remove domain button in the dashboard.
Show or reset the password
- Show password (eye icon on the Local registry card, owners and admins) reads the stored password again; it hides itself after a minute and each reveal is recorded in the audit log.
- Reset password (owner) generates a new password, rewrites the registry's login file and restarts the container briefly. Images are not touched. Every client and CI job that used the old password has to run
docker loginagain. If anything fails midway, the old password stays in force. - From the CLI:
aoox registry password --showoraoox registry password --reset --yes.
Browsing & cleaning up contents
- The Local registry tab lists repository → tag along with digest and size. Repository name =
<project-slug>/<app-slug>. - Deleting a tag deletes the manifest. Other tags pointing at the same digest disappear too — the UI warns about it.
- Garbage collect (owner) reclaims disk space after tags are deleted — it runs inside the running registry container; there's a dry run mode to preview what would be removed.
- Delete image (the button on each repository's header row) removes the whole repository, not just one tag: every tag and manifest, its folder in storage, then an automatic garbage collect — different from deleting tags one at a time, and this is what you need when a repository still shows “0 tags” and won't disappear from the list no matter how many times garbage collect runs (garbage collect only reclaims unused blobs, never the repository's own folder). Its confirmation dialog shows which applications' current image looks like it came from this repository — a rollback or a build-free redeploy to that image will fail once it's gone.
- Deleting a tag still used by a running container doesn't stop it, but rolling back to that version is no longer possible.
- Deleting the registry (owner) stops the container; you'll be asked about the data volume. Deploys can't happen again until it's provisioned again.
Disk maintenance
So old images don't fill up the disk, aoox has automatic cleanup that runs every night at 04:30 (server time zone, UTC unless you set TZ) and can also be triggered manually.
| What's cleaned | Rule |
|---|---|
| Old deployments per application | The Deployment history field in an application's Settings (default 10): successful deployments beyond this count are pruned, and their image is deleted both locally and from the registry. The image currently in use is never touched. |
| Failed/queued deployments | Rows (log only) older than 30 days are deleted. |
| Dangling images & build cache | Cleaned on the local daemon — application images aren't touched. |
| Registry | Garbage collect runs after pruning. |
Docker → disk strip and Details
The strip at the top of the Docker page (host selected) shows total usage and what can be freed; Details shows image, container, volume, and build cache usage, how much can be reclaimed, how many deployments can be pruned, and the most recent cleanup report.
Clean now (owner)
Runs the same cycle on demand. The report: deployments pruned, images deleted, bytes reclaimed, registry GC status, and any errors.
External registry
External registry tab → Add registry (owner/admin): register credentials for Docker Hub, GHCR, GitLab, etc.
| Field | Example |
|---|---|
| Name | Company GHCR |
| URL | ghcr.io |
| Username / Password / token | A token with read access (and write if needed); stored encrypted |
| Image prefix | Optional, e.g. myorg → the image is referenced as <url>/<prefix>/<app> |
Common pitfalls
| Symptom | Cause & fix |
|---|---|
| Deploy fails: No self-hosted registry | Provision the local registry first. |
| docker push to IP:5000: connection refused | The registry port listens on 127.0.0.1 by default. Use a custom domain, or set REGISTRY_PUBLISH_ADDRESS=0.0.0.0 and recreate the registry container. |
| Manual push: http: server gave HTTP response to HTTPS client | REGISTRY_PUBLIC_HOST isn't localhost, without TLS. Add it to insecure-registries, or set up a TLS reverse proxy. |
| Disk still full after deleting tags | Run Garbage collect, or Clean now in the disk strip at the top of the Docker page. |
| A “0 tags” repository won’t disappear even after Garbage collect | Garbage collect never deletes the repository itself, only unused blobs. Use the Delete image button on that repository's header row. |
| Rollback fails: image not found | The tag was already pruned by retention or deleted manually. Redeploy that commit. |
| Registry card: Docker unreachable | DOCKER_GID is wrong or the socket isn't mounted — see Troubleshooting. |
Next steps
Found a mistake? Edit this page on GitHub ↗