Skip to main content

Pushing Images

Learn how to distribute Docker images to your cluster machines and external registries.

Overview

After building an image, it exists only on your local machine. To deploy it to your cluster, the image must be pushed to cluster machines.

Uncloud provides two ways to push images:

  1. Push to cluster machines (--push) - Direct transfer to cluster machines
  2. Push to external registry (--push-registry) - Upload to Docker Hub, GitHub Container Registry, etc.

Push to Cluster Machines

The most common workflow: build locally and push directly to your cluster.

Basic Push

# Build and push in one command
uc build --push

This:

  1. Builds images locally
  2. Connects to your cluster
  3. Pushes images to target machines via gRPC

Automatic Push with Deploy

When using uc deploy, images are automatically pushed:

uc deploy

This does:

  1. Build images locally
  2. Push images to cluster
  3. Deploy containers

Equivalent to:

uc build
uc build --push # (happens automatically)
uc deploy --no-build

Target Specific Machines

Push to specific machines:

# Push to specific machines
uc build --push -m machine1,machine2

# Or
uc build --push --machines machine1,machine2

Service-specific Machine Targeting

Using x-machines in compose.yaml:

services:
web:
build: .
image: myapp:latest
x-machines:
- machine1
- machine2
deploy:
replicas: 2
uc build --push

Images are pushed only to machine1 and machine2 (as specified in x-machines).

Push All Services

Push all built images:

uc build --push

Push Specific Services

Push only certain service images:

# Build and push only frontend
uc build frontend --push

# Build and push frontend and API
uc build frontend api --push

Push to External Registries

Push images to Docker Hub, GitHub Container Registry, or private registries.

Docker Hub

Tag your image for Docker Hub:

services:
web:
build: .
image: username/myapp:v1.2.3

Authenticate:

docker login

Build and push:

uc build --push-registry

Your image is now available at docker.io/username/myapp:v1.2.3.

GitHub Container Registry

Tag for GitHub:

services:
web:
build: .
image: ghcr.io/username/myapp:v1.2.3

Authenticate:

echo $GITHUB_TOKEN | docker login ghcr.io -u username --password-stdin

Push:

uc build --push-registry

Private Registry

Tag for your registry:

services:
web:
build: .
image: registry.example.com/myapp:v1.2.3

Authenticate:

docker login registry.example.com

Push:

uc build --push-registry

Push Strategies

Strategy 1: Direct Cluster Push (Default)

Best for most use cases:

uc build --push

Pros:

  • Fast (direct machine-to-machine transfer)
  • No external dependencies
  • Works behind firewalls

Cons:

  • Images only available in one cluster
  • Requires SSH access to machines

Strategy 2: Registry-based

Push to registry, pull from cluster:

# Build and push to registry
uc build --push-registry

# Update compose.yaml to use registry image
# Deploy (pulls from registry)
uc deploy --no-build

Pros:

  • Images available to multiple clusters
  • Version history in registry
  • Can deploy from any machine

Cons:

  • Slower (upload + download)
  • Requires registry setup
  • Potential bandwidth costs

Strategy 3: Hybrid

Push to both cluster and registry:

# Build once
uc build

# Push to cluster for immediate deployment
uc build --push

# Also push to registry for backup/sharing
uc build --push-registry

Pros:

  • Fast deployment
  • Images backed up in registry
  • Can rollback from registry

Cons:

  • Double push time
  • More bandwidth usage

Understanding Image Distribution

How Cluster Push Works

Your Machine
↓ (build)
Local Docker daemon
↓ (save image as tar)
SSH/gRPC connection
↓ (transfer tar over network)
Cluster machine
↓ (load tar into Docker)
Docker daemon on machine

Parallel Push

For multiple machines, Uncloud pushes in parallel:

uc build --push
Your Machine

├─→ machine1 (parallel)
├─→ machine2 (parallel)
└─→ machine3 (parallel)

Progress Tracking

Watch push progress:

Pushing images to cluster...
✓ Pushed myapp:latest to machine-1 (2.3 MB in 1.2s)
✓ Pushed myapp:latest to machine-2 (2.3 MB in 1.5s)
✓ Pushed myapp:latest to machine-3 (2.3 MB in 1.8s)

Image Tagging

Semantic Versioning

Tag images with version numbers:

services:
web:
build: .
image: myapp:1.2.3
uc build --push

Git-based Tagging

Use git commit SHA:

services:
web:
build: .
image: myapp:${GIT_SHA}
GIT_SHA=$(git rev-parse --short HEAD)
uc build --push

Environment-based Tagging

Different tags for different environments:

services:
web:
build: .
image: myapp:${ENV:-dev}
# Development
uc build --push

# Production
ENV=prod uc build --push

Multiple Tags

Tag the same image multiple times:

services:
web:
build: .
image: myapp:1.2.3

x-tags:
- myapp:latest
- myapp:stable

After building:

docker tag myapp:1.2.3 myapp:latest
docker tag myapp:1.2.3 myapp:stable
uc build --push

CI/CD Integration

GitHub Actions

name: Build and Push

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Build image
run: uc build --build-arg VERSION=${{ github.sha }}

- name: Push to cluster
run: uc build --push

- name: Deploy
run: uc deploy --no-build --yes

GitLab CI

stages:
- build
- deploy

build:
stage: build
script:
- uc build --build-arg VERSION=$CI_COMMIT_SHA
- uc build --push-registry

deploy:
stage: deploy
script:
- uc deploy --no-build --yes
only:
- main

Push to Registry in CI

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push
run: uc build --push-registry

Optimizing Push Performance

Reduce Image Size

Smaller images push faster:

# Use alpine base images
FROM node:20-alpine

# Multi-stage builds
FROM node:20 AS builder
RUN npm run build

FROM node:20-alpine
COPY --from=builder /app/dist /app/dist

Use Build Cache

Leverage Docker layer caching:

# Dependencies (cached unless package.json changes)
COPY package*.json ./
RUN npm install

# Source code (only this rebuilds on changes)
COPY . .

.dockerignore

Exclude unnecessary files from build context:

node_modules
.git
*.log
.env*
dist/
build/

Compress Images

Multi-stage builds automatically reduce image size:

FROM golang:1.21 AS builder
COPY . .
RUN go build -o app

FROM alpine:latest
COPY --from=builder /app /app

Final image only contains compiled binary, not build tools.

Troubleshooting

Push Fails to Cluster

Check connectivity:

# Verify SSH access
ssh user@machine1

# Check Uncloud daemon
ssh user@machine1 'systemctl status uncloud'

Push to Registry Fails

Verify authentication:

docker login
docker push myapp:latest

Check credentials:

cat ~/.docker/config.json

Image Too Large

If push is slow due to large images:

  1. Use multi-stage builds
  2. Use smaller base images (alpine)
  3. Add .dockerignore
  4. Remove unnecessary files in Dockerfile

Out of Disk Space on Machines

Clean up old images on cluster machines:

ssh user@machine1 'docker image prune -a'

Or configure automatic cleanup.

Registry Rate Limits

Docker Hub has rate limits for pulls. Solutions:

  1. Authenticate (higher limits for authenticated users)
  2. Use Docker Hub Pro/Team
  3. Use alternative registries (GitHub, GitLab)

Push Hangs

If push seems stuck:

  1. Check network connectivity
  2. Check machine disk space
  3. Verify Docker daemon is running on machines
  4. Check firewall rules

Security Considerations

Private Registries

Use authentication for private registries:

docker login registry.example.com

Configure registry credentials on all machines that need to pull images.

Image Scanning

Scan images for vulnerabilities before pushing:

# Build image
uc build

# Scan with Docker Scout
docker scout cves myapp:latest

# If safe, push
uc build --push

Signed Images

Sign images for verification:

# Enable Docker Content Trust
export DOCKER_CONTENT_TRUST=1

# Push will sign image
uc build --push-registry

Best Practices

  1. Version your images with semantic versioning
  2. Use specific tags in production (not :latest)
  3. Push to registry for backup and sharing
  4. Optimize image size for faster pushes
  5. Scan images for vulnerabilities before pushing
  6. Use multi-arch images for heterogeneous clusters
  7. Tag with git SHA for traceability
  8. Clean up old images regularly

Next Steps