▸ Push docker images directly to remote servers without an external registry ◂
Unregistry is a lightweight container image registry that stores and serves images directly from your Docker daemon's storage.
The included docker pussh command (extra 's' for SSH) lets you push images straight to remote Docker servers over SSH. It transfers only the missing layers, making it fast and efficient.
docker-pussh-demo.mp4
The problem
You've built a Docker image locally. Now you need it on your server. Your options suck:
Docker Hub / GitHub Container Registry - Your code is now public, or you're paying for private repos
- Your code is now public, or you're paying for private repos Self-hosted registry - Another service to maintain, secure, and pay for storage
- Another service to maintain, secure, and pay for storage Save/Load - docker save | ssh | docker load transfers the entire image, even if 90% already exists on the server
- transfers the entire image, even if 90% already exists on the server Rebuild remotely - Wastes time and server resources. Plus now you're debugging why the build fails in production
You just want to move an image from A to B. Why is this so hard?
The solution
docker pussh myapp:latest user@server
That's it. Your image is on the remote server. No registry setup, no subscription, no intermediate storage, no exposed ports. Just a direct transfer of the missing layers over SSH.
Here's what happens under the hood:
Establishes SSH tunnel to the remote server Starts a temporary unregistry container Forwards a random localhost port to the unregistry port over the tunnel docker push to unregistry through the forwarded port, transferring only the layers that don't already exist remotely. The transferred image is instantly available on the remote Docker daemon Stops the unregistry container and closes the SSH tunnel
It's like rsync for Docker images — simple and efficient.
Note Unregistry was created for Uncloud, a lightweight tool for deploying containers across multiple Docker hosts. We needed something simpler than a full registry but more efficient than save/load.
Installation
macOS/Linux via Homebrew
brew install psviderski/tap/docker-pussh
After installation, to use docker-pussh as a Docker CLI plugin ( docker pussh command) you need to create a symlink:
mkdir -p ~ /.docker/cli-plugins ln -sf $( brew --prefix ) /bin/docker-pussh ~ /.docker/cli-plugins/docker-pussh
macOS/Linux via direct download
# Download the latest version curl -sSL https://raw.githubusercontent.com/psviderski/unregistry/main/docker-pussh \ -o ~ /.docker/cli-plugins/docker-pussh # Make it executable chmod +x ~ /.docker/cli-plugins/docker-pussh
Windows
Windows is not currently supported, but you can try using WSL 2 with the above Linux instructions.
Verify installation
docker pussh --help
Usage
Push an image to a remote server. Please make sure the SSH user has permissions to run docker commands (user is root or non-root user is in docker group). If sudo is required, ensure the user can run sudo docker without a password prompt.
docker pussh myapp:latest [email protected]
With SSH key authentication if the private key is not added to your SSH agent:
docker pussh myapp:latest [email protected] -i ~ /.ssh/id_rsa
Using a custom SSH port:
docker pussh myapp:latest user@server:2222
Push a specific platform for a multi-platform image. The local Docker has to use containerd image store to support multi-platform images.
docker pussh myapp:latest user@server --platform linux/amd64
Use cases
Deploy to production servers
Build locally and push directly to your production servers. No middleman.
docker build --platform linux/amd64 -t myapp:1.2.3 . docker pussh myapp:1.2.3 deploy@prod-server ssh deploy@prod-server docker run -d myapp:1.2.3
CI/CD pipelines
Skip the registry complexity in your pipelines. Build and push directly to deployment targets.
- name : Build and deploy run : | docker build -t myapp:${{ github.sha }} . docker pussh myapp:${{ github.sha }} deploy@staging-server
Homelab and air-gapped environments
Distribute images in isolated networks without exposing them to the internet.
docker pussh image:latest [email protected]
Requirements
On local machine
Docker CLI with plugin support (Docker 19.03+)
OpenSSH client
On remote server
Docker is installed and running
SSH user has permissions to run docker commands (user is root or non-root user is in docker group)
commands (user is or non-root user is in group) If sudo is required, ensure the user can run sudo docker without a password prompt
Tip The remote Docker daemon works best with containerd image store enabled. This allows unregistry to access images more efficiently. Add the following configuration to /etc/docker/daemon.json on the remote server and restart the docker service: { "features" : { "containerd-snapshotter" : true } }
Advanced usage
Running unregistry standalone
Sometimes you want a local registry without the overhead. Unregistry works great for this:
# Run unregistry locally and expose it on port 5000 docker run -d -p 5000:5000 --name unregistry \ -v /run/containerd/containerd.sock:/run/containerd/containerd.sock \ ghcr.io/psviderski/unregistry # Use it like any registry docker tag myapp:latest localhost:5000/myapp:latest docker push localhost:5000/myapp:latest
Custom SSH options
Need custom SSH settings? Use the standard SSH config file:
# ~/.ssh/config Host prod-server HostName server.example.com User deploy Port 2222 IdentityFile ~ /.ssh/deploy_key # Now just use docker pussh myapp:latest prod-server
Contributing
Found a bug or have a feature idea? We'd love your help!
🐛 Found a bug? Open an issue
💡 Have ideas or need help? Join Uncloud Discord community where we discuss features, roadmap, implementation details, and help each other out.
Inspiration & acknowledgements
Spegel - P2P container image registry that inspired me to implement a registry that uses containerd image store as a backend.
Docker Distribution - the bulletproof Docker registry implementation that unregistry uses as a base.