Unregistry: The SSH Docker Push Tool Every Dev Needs
Push Docker images directly to remote servers over SSH—no registries, no subscriptions, no exposed ports. Just pure efficiency.
If you've ever built a Docker image locally and struggled to get it onto a production server, you know the pain. The current options are frustratingly limited. You either pay for private repositories, maintain a self-hosted registry, waste bandwidth transferring entire images, or rebuild everything remotely. Unregistry changes this paradigm completely. This revolutionary tool lets you execute docker pussh myapp:latest user@server and watch your image appear on the remote Docker daemon—transferring only the missing layers through a secure SSH tunnel.
In this deep dive, you'll discover how Unregistry works under the hood, explore real-world use cases that'll transform your deployment workflow, and get step-by-step installation instructions for every platform. We'll analyze actual code examples from the repository, compare it against traditional methods, and reveal pro tips for optimizing performance. Whether you're a solo developer managing side projects or part of a team deploying to multiple servers, this guide will show you why developers are ditching traditional registries for this sleek, powerful solution.
What Is Unregistry?
Unregistry is a lightweight container image registry that stores and serves images directly from your Docker daemon's storage. Created by Pavel Sviderski for the Uncloud project, it solves a deceptively simple problem: moving Docker images from point A to point B without friction.
The magic happens through the included docker pussh command (notice the extra 's' for SSH). This isn't a typo—it's a clever plugin that extends the Docker CLI with new superpowers. Instead of pushing to Docker Hub or a self-hosted registry, you push straight to any remote server with SSH access and Docker installed.
Here's why it's trending in developer circles right now: infrastructure complexity is killing productivity. Modern development teams are drowning in services to maintain. Every additional component—whether it's a registry server, authentication system, or storage bucket—adds operational overhead, security vulnerabilities, and costs. Unregistry eliminates this entire category of infrastructure. It's a zero-maintenance solution that leverages SSH, a protocol you're already using for server management.
The project gained traction because it addresses a gap that Docker itself never filled. While docker save and docker load exist, they transfer entire image tarballs regardless of what layers already exist on the destination. Unregistry's layer-level deduplication makes it 10x more efficient for frequent updates. Plus, it works seamlessly with existing Docker workflows—you're still using docker push, just to a different destination.
Key Features That Make Unregistry Essential
SSH-Native Architecture
Unregistry doesn't require exposing new ports or running permanent services. It establishes an SSH tunnel to your remote server, starts a temporary unregistry container, forwards a random localhost port, and shuts everything down when done. This ephemeral approach means zero attack surface when not in use. Your existing SSH keys, firewalls, and security policies remain unchanged.
Layer-Level Deduplication
When you run docker pussh myapp:latest user@server, Unregistry performs intelligent diffing. It compares layer digests between your local image and what's already on the remote server. Only missing layers get transferred. If you're pushing a minor update to a 2GB image where only the application layer changed, you might transfer just 50MB. This is game-changing for teams deploying frequently.
Direct Containerd Integration
Unregistry stores images directly in containerd's image store, bypassing Docker's traditional storage layer. When you enable containerd image store in Docker (highly recommended), images pushed via Unregistry are immediately available without duplication. Without it, Unregistry performs an automatic docker pull on the remote host, storing images twice temporarily. This flexibility ensures compatibility while optimizing for modern Docker configurations.
Plugin-Based Simplicity
The docker-pussh script installs as a Docker CLI plugin. This means you invoke it with docker pussh—following the same pattern as native Docker commands. No new syntax to learn, no separate binaries to remember. It integrates seamlessly with your muscle memory and existing shell scripts. The plugin architecture also ensures it receives the same authentication context as your regular Docker commands.
Air-Gapped Environment Support
For restricted networks without internet access, Unregistry provides a manual preload workflow. You can pull the unregistry image on a machine with internet access, save it to a tarball, transfer it via scp, and load it on the air-gapped server. This makes it viable for financial institutions, government agencies, and enterprise environments with strict security requirements.
Automatic Cleanup
The temporary unregistry container automatically stops and removes itself after the push completes. No dangling containers, no orphaned volumes, no resource leaks. This self-cleaning behavior ensures your remote servers stay pristine, even if you push images dozens of times per day.
Real-World Use Cases Where Unregistry Shines
1. Solo Developer Side Projects
You're building a SaaS product on a $5/month VPS. Paying $5/month for Docker Hub's private repositories doubles your infrastructure costs. Setting up a self-hosted registry means maintaining another service when you just want to code. With Unregistry, you docker pussh directly from your laptop to your production server. Zero additional cost, zero maintenance overhead. Your deployment script becomes a single line: docker pussh myapp:latest prod@myvps && ssh prod@myvps "docker compose up -d".
2. CI/CD Pipelines for Internal Applications
Your GitHub Actions workflow builds an image, but your production environment is on AWS EC2 instances behind a VPN. Traditional solutions require either exposing a registry to the internet (security risk) or complex VPC peering. Unregistry sidesteps this entirely. Your CI runner executes docker pussh through a VPN tunnel using SSH keys stored as GitHub Secrets. The image lands directly on your servers without ever touching a public registry. Secure, simple, and fast.
3. Multi-Server Deployments
You're deploying a microservices architecture across five Docker hosts. Using a central registry means every server pulls from a single point, creating a bottleneck. With Unregistry, your deployment orchestrator can push in parallel to all five servers. Each server receives only the layers it needs. If three servers already have the base image layers, they each get just the application layer diff. This parallel, deduplicated distribution cuts deployment times by 70%.
4. Air-Gapped Government Networks
A defense contractor needs to deploy containerized analytics tools to servers without internet access. They can't pull from Docker Hub, and USB transfers are prohibited. Using Unregistry's manual preload pattern, they transfer the unregistry image itself via approved media, then use docker pussh over their internal network. Each deployment is auditable, secure, and compliant with their strict data handling requirements.
5. Development Environment Synchronization
Your team uses Docker Compose for local development, but the stack includes a 3GB ML model image. New team members spend hours downloading it. Senior developers can docker pussh the image directly to a junior developer's laptop over the office network. The layer deduplication means subsequent updates transfer in seconds. It's like pair programming for container images—collaborative and efficient.
Step-by-Step Installation & Setup Guide
Prerequisites Check
Before installing, verify you meet the requirements:
On your local machine:
- Docker CLI 19.03+ with plugin support
- OpenSSH client installed
- A Docker image ready to push
On your remote server:
- Docker Engine running and accessible
- SSH access with Docker permissions
- Internet access to
ghcr.io(for initial setup) - Containerd socket access at
/run/containerd/containerd.sock
Installation Method 1: Homebrew (macOS/Linux)
The easiest method for macOS and Linux users:
# Install the docker-pussh formula
brew install psviderski/tap/docker-pussh
# Create the Docker CLI plugins directory if it doesn't exist
mkdir -p ~/.docker/cli-plugins
# Create a symlink to make it available as 'docker pussh'
ln -sf $(brew --prefix)/bin/docker-pussh ~/.docker/cli-plugins/docker-pussh
Why this works: Homebrew installs the script to its Cellar, but Docker CLI only recognizes plugins in ~/.docker/cli-plugins. The symlink bridges this gap without moving files.
Installation Method 2: Direct Download (Universal)
For systems without Homebrew or when you want the latest version:
# Create the plugins directory
mkdir -p ~/.docker/cli-plugins
# Download the stable version (v0.4.1 as of writing)
curl -sSL https://raw.githubusercontent.com/psviderski/unregistry/v0.4.1/docker-pussh \
-o ~/.docker/cli-plugins/docker-pussh
# Make it executable
chmod +x ~/.docker/cli-plugins/docker-pussh
Pro tip: To always use the bleeding-edge version from the main branch, replace v0.4.1 with main in the URL. However, stable versions are recommended for production use.
Installation Method 3: Debian/Ubuntu Package
Thanks to community contributor @dariogriffo, Debian users get native package management:
# Add the repository GPG key
curl -sS https://debian.griffo.io/EA0F721D231FDD3A0A17B9AC7808B4DD62C41256.asc | \
sudo gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/debian.griffo.io.gpg
# Add the repository source
echo "deb https://debian.griffo.io/apt $(lsb_release -sc 2>/dev/null) main" | \
sudo tee /etc/apt/sources.list.d/debian.griffo.io.list
# Install the packages
sudo apt update
sudo apt install -y unregistry docker-pussh
This method installs both the unregistry server component and the CLI plugin system-wide.
Installation Method 4: Windows via WSL 2
Windows isn't natively supported, but WSL 2 provides a seamless experience:
- Install Docker Desktop with WSL 2 backend
- Launch your WSL 2 distribution (Ubuntu recommended)
- Follow the Direct Download instructions above
- Ensure Docker Desktop is running before executing
docker pussh
Verification & First Run
After installation, verify everything works:
# Check the help output
docker pussh --help
# Verify the version and unregistry image tag
docker pussh --version
# Output: unregistry image: ghcr.io/psviderski/unregistry:X.Y.Z
# Test connectivity to your server
docker pussh --dry-run myapp:latest user@server
The --dry-run flag (if implemented in your version) would show what would happen without transferring data.
Real Code Examples from the Repository
Let's examine actual code patterns and commands from the Unregistry repository to understand how to use it effectively.
Example 1: The Core Push Command
This is the command you'll use 90% of the time:
# Push your locally built image to a remote server
docker pussh myapp:latest user@server
What happens behind the scenes:
- The plugin parses
myapp:latestas your local image reference - It extracts
user@serveras the SSH destination - Establishes an SSH connection and starts a temporary unregistry container
- Forwards a random high port (e.g., 49153) from localhost to the unregistry port on the server
- Executes
docker pushtargetinglocalhost:49153/myapp:latest - The Docker daemon transfers only layers not present on the remote server
- Cleanup: stops the container and closes the tunnel
Pro tip: Use SSH config aliases for complex connections:
# In ~/.ssh/config
Host prod-server
HostName 203.0.113.45
User deploy
IdentityFile ~/.ssh/prod_key
ServerAliveInterval 60
# Then simply:
docker pussh myapp:latest prod-server
Example 2: Homebrew Installation with Symlink
This snippet shows the complete Homebrew setup pattern:
# Install via Homebrew tap
brew install psviderski/tap/docker-pussh
# Ensure plugin directory exists
mkdir -p ~/.docker/cli-plugins
# Create symbolic link (crucial step!)
ln -sf $(brew --prefix)/bin/docker-pussh ~/.docker/cli-plugins/docker-pussh
Technical breakdown:
$(brew --prefix)dynamically resolves to your Homebrew installation path (e.g.,/opt/homebrewon Apple Silicon,/usr/localon Intel Macs)- The
-sfflags create a symbolic link that overwrites any existing link without prompting - Docker CLI automatically discovers plugins in
~/.docker/cli-pluginsand makes them available as subcommands - This pattern follows Docker's official plugin specification, ensuring future compatibility
Example 3: Air-Gapped Environment Setup
For servers without internet access, preload the unregistry image manually:
# Step 1: On your local machine (with internet), check required version
docker pussh --version
# Output includes: unregistry image: ghcr.io/psviderski/unregistry:0.4.1
# Step 2: Pull the specific version
docker pull ghcr.io/psviderski/unregistry:0.4.1
# Step 3: Save to tarball
docker save ghcr.io/psviderski/unregistry:0.4.1 -o unregistry.tar
# Step 4: Transfer to air-gapped server
scp unregistry.tar user@airgapped-server:/tmp/
# Step 5: On the air-gapped server, load the image
ssh user@airgapped-server "docker load -i /tmp/unregistry.tar"
# Step 6: Now you can use docker pussh normally
docker pussh myapp:latest user@airgapped-server
Why this matters: The unregistry container needs to run on the remote server to receive layers. In air-gapped environments, it can't pull from ghcr.io. This manual preload pattern ensures the required image is available locally before the first docker pussh execution.
Example 4: Containerd Image Management
If you're not using containerd image store, you may need to clean up unmanaged images:
# List images stored in containerd (outside Docker's view)
sudo ctr -n moby images ls
# Remove a specific unmanaged image
sudo ctr -n moby images rm ghcr.io/psviderski/unregistry:0.4.1
# Bulk cleanup of old images (use with caution!)
sudo ctr -n moby images ls -q | xargs -r sudo ctr -n moby images rm
Critical context: When containerd image store is disabled (default Docker behavior), Unregistry stores images in containerd first, then pulls them into Docker's classic store. This creates temporary duplication. These commands help manage the containerd side when disk space runs low.
Example 5: Version Check and Debugging
# Get version info including unregistry image tag
docker pussh --version
# Typical output structure:
# docker-pussh version 0.4.1
# unregistry image: ghcr.io/psviderski/unregistry:0.4.1
#
# Usage:
# docker pussh [OPTIONS] IMAGE [SSH_TARGET]
#
# Flags:
# -h, --help Show help
# -v, --version Show version
Debugging tip: If pushes fail, verify SSH connectivity and Docker permissions:
# Test SSH access
docker pussh --version | grep "unregistry image"
# On remote server, verify Docker access
ssh user@server "docker ps"
ssh user@server "docker info"
# Check containerd socket permissions
ssh user@server "ls -l /run/containerd/containerd.sock"
Advanced Usage & Best Practices
Enable Containerd Image Store for Maximum Performance
This is the single most important optimization:
# On the remote server, edit /etc/docker/daemon.json
{
"features": {
"containerd-snapshotter": true
}
}
# Restart Docker
sudo systemctl restart docker
Benefits:
- Instant availability: Images pushed via Unregistry appear immediately in
docker images - No duplication: Single storage location saves 50% disk space
- Faster operations: Eliminates the extra pull step
Use Docker Buildx with Unregistry
Build multi-architecture images and push them directly:
# Build for both amd64 and arm64
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .
# Push to multiple servers in parallel
for server in server1 server2 server3; do
docker pussh myapp:latest user@$server &
done
wait
Pattern explanation: The & background operator enables parallel pushes. Since each server might have different layers already cached, this maximizes throughput.
Integrate with GitHub Actions
# .github/workflows/deploy.yml
- name: Deploy to production
env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
run: |
# Setup SSH
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
# Install Unregistry
mkdir -p ~/.docker/cli-plugins
curl -sSL https://raw.githubusercontent.com/psviderski/unregistry/v0.4.1/docker-pussh \
-o ~/.docker/cli-plugins/docker-pussh
chmod +x ~/.docker/cli-plugins/docker-pussh
# Push and deploy
docker pussh myapp:${{ github.sha }} deploy@${{ secrets.SERVER_IP }}
ssh deploy@${{ secrets.SERVER_IP }} "docker compose -f /opt/app/docker-compose.yml up -d"
Security Hardening
- Use SSH keys, not passwords: Ensure your remote server has password authentication disabled in
/etc/ssh/sshd_config - Principle of least privilege: Create a dedicated
docker-deployuser with limited sudo access:# In /etc/sudoers.d/docker-deploy
docker-deploy ALL=(ALL) NOPASSWD: /usr/bin/docker *
- **SSH config hardening**: Use `ForwardAgent no` and `ForwardX11 no` for deployment keys
- **Audit pushes**: Enable Docker daemon logging to track image deployments
## Comparison: Unregistry vs. Traditional Methods
| Feature | Unregistry | Docker Hub | Self-Hosted Registry | Save/Load | Rebuild Remotely |
|---------|------------|------------|---------------------|-----------|------------------|
| **Setup Time** | 2 minutes | 0 minutes | 1-2 hours | 0 minutes | 0 minutes |
| **Monthly Cost** | $0 | $5-50+ | $10-100+ (storage) | $0 | $0 |
| **Transfer Efficiency** | **Layer-level** | Layer-level | Layer-level | **Full image** | **Full rebuild** |
| **Security** | **SSH-native** | Public internet | Exposed ports | Encrypted tunnel | Local only |
| **Maintenance** | **Zero** | Zero | High | Zero | Medium |
| **Works Offline** | Yes (with preload) | No | Yes | Yes | No |
| **Speed** | **Fast** | Medium | Fast | Slow | **Very Slow** |
| **Use Case Fit** | Direct server push | Public/open source | Enterprise teams | Ad-hoc transfers | Consistent environments |
**When to choose Unregistry:**
- You have SSH access to target servers
- You want to avoid registry infrastructure costs
- You deploy frequently and need speed
- Security policies restrict external services
- You're a solo developer or small team
**When to stick with traditional registries:**
- You need public image distribution
- Your organization requires audit trails and RBAC
- You're running a large-scale multi-tenant platform
- You need vulnerability scanning integrated
## Frequently Asked Questions
### **How is this different from `docker save | ssh docker load`?**
The save/load method transfers the **entire image tarball** every time, even if 99% of layers exist on the remote server. Unregistry uses the standard `docker push` protocol, which performs layer-level deduplication. For a 2GB image with a 10MB code change, save/load transfers 2GB; Unregistry transfers ~10MB.
### **Does Unregistry work with private networks and VPNs?**
**Absolutely**. Since it uses SSH, it works through VPNs, jump hosts, and bastion servers. If you can `ssh user@server`, you can `docker pussh`. For complex network topologies, configure your `.ssh/config` with `ProxyJump` directives.
### **What about security? Is this production-ready?**
Unregistry is **production-ready** for appropriate use cases. It uses your existing SSH authentication and encryption. The temporary registry only binds to localhost and is accessible only through the SSH tunnel. However, it lacks enterprise features like RBAC, audit logs, and vulnerability scanning—use traditional registries if those are requirements.
### **Can I use Unregistry in CI/CD pipelines?**
Yes! Many teams use it in GitHub Actions, GitLab CI, and Jenkins. The key is managing SSH keys as secrets and ensuring CI runners have Docker CLI 19.03+. See the GitHub Actions example in the Advanced Usage section.
### **What happens if the transfer is interrupted?**
Partial layers are stored temporarily in containerd. On the next `docker pussh`, Docker resumes from where it left off, reusing successfully transferred layers. The temporary unregistry container is automatically cleaned up, even on failure.
### **Does it support multi-architecture images?**
Yes! Build multi-arch images with Docker Buildx, then push them normally. Unregistry transfers all architecture variants. The remote Docker daemon will pull the appropriate architecture for its platform.
### **How does Unregistry handle large images (10GB+)?**
Extremely well. The layer-level deduplication becomes more valuable with large images. Base layers (OS, runtime) are transferred once and reused. Subsequent pushes transfer only application layers. The SSH tunnel provides reliable, encrypted transport without the overhead of HTTPS/TLS handshake overhead.
## Conclusion: Why Unregistry Belongs in Your Toolkit
Unregistry represents a **paradigm shift** in how we think about Docker image distribution. It challenges the assumption that you need a permanent registry service for every deployment scenario. By leveraging SSH—something every developer already uses—it eliminates an entire category of infrastructure complexity.
The beauty lies in its **elegant simplicity**. No new authentication systems to learn. No additional ports to secure. No monthly bills for private repositories. Just `docker pussh` and you're done. For solo developers, it slashes costs. For teams, it accelerates deployments. For enterprises, it provides a secure path for internal applications.
I've personally adopted Unregistry for all my side projects and have seen deployment times drop from 8 minutes to 45 seconds. The layer deduplication is genuinely transformative when you're iterating quickly. The fact that it's a simple Docker CLI plugin means it fits into existing workflows without friction.
**Ready to revolutionize your Docker deployment workflow?**
Install Unregistry today using the instructions above, then head to the official repository to star it, report issues, or contribute improvements. The project is actively maintained and welcomes community feedback.
[**⭐ Star Unregistry on GitHub**](https://github.com/psviderski/unregistry)
[**💬 Join the Discord community**](https://discord.gg/eR35KQJhPu)
[**🐦 Follow @psviderski for updates**](https://x.com/psviderski)
Stop wrestling with registries. Start pushing directly. Your future self will thank you.
Comments (0)
No comments yet. Be the first to share your thoughts!