Software Engineer's Blog

Local Docker Registry & Multi-Service Build/Deploy Guide

Local Docker Registry & Multi-Service Build/Deploy Guide

Using GCP Artifact Registry incurs small costs, and Docker Hub allows only one private repository on the free tier.

This guide explains how to build Docker images on a development PC, push them to a local registry on a LAN, and deploy them to a test server without cloud costs.

Background

Previously, the test environment built everything directly on the test laptop:

[Old Approach]
Test Laptop: git pull → mvn build → docker build → docker run

Problems:

  • Requires full build environment (JDK, Maven, Node.js, etc.) on the test server
  • Slow build times
  • Risk of inconsistent environments

Improved approach:

[Improved Approach]
Dev PC: code change → build-push.sh → push to local registry
Test Server: docker-compose pull → docker-compose up

Environment Setup

RoleMachineOS
Dev PC750XDAUbuntu
Test ServerGU502DUUbuntu
  • Test server IP: 192.168.1.144
  • Registry port: 5000

Step 1: Install Docker Registry on Test Server

Create a dedicated user

sudo useradd -m -s /bin/bash dkr
sudo usermod -aG docker dkr
sudo usermod -aG sudo dkr

Open firewall port

sudo ufw allow 5000/tcp
sudo ufw status

Run registry container

su - dkr

docker run -d \
  -p 5000:5000 \
  --restart=always \
  --name registry \
  registry:2

Verify

curl http://192.168.1.144:5000/v2/_catalog

Expected:

{"repositories":[]}

Step 2: Configure Insecure Registry (Dev PC)

sudo tee /etc/docker/daemon.json <<EOF
{
  "insecure-registries": ["192.168.1.144:5000"]
}
EOF

sudo systemctl restart docker

Step 3: Build & Push Services

Java / Spring Boot

./mvnw clean package -DskipTests

docker build -t 192.168.1.144:5000/user-service:latest .
docker push 192.168.1.144:5000/user-service:latest

Frontend (Next.js)

./docker-build.sh

docker tag frontend:latest 192.168.1.144:5000/frontend:latest
docker push 192.168.1.144:5000/frontend:latest

Python / FastAPI (with BuildKit Secret)

DOCKER_BUILDKIT=1 docker build \
  --secret id=gcp_sa_key,src=/path/to/key.json \
  -t 192.168.1.144:5000/chat-service:latest \
  .

docker push 192.168.1.144:5000/chat-service:latest

Step 4: build-push.sh Script

Example:

#!/bin/bash
set -e

REGISTRY="192.168.1.144:5000"
IMAGE="user-service"

./mvnw clean package -DskipTests

docker build -t ${REGISTRY}/${IMAGE}:latest .
docker push ${REGISTRY}/${IMAGE}:latest

Step 5: Verify Registry

curl http://192.168.1.144:5000/v2/_catalog

Docker Hub vs Local Registry

FeatureLocal RegistryDocker Hub
CostFreeLimited
SpeedFast (LAN)Slower
SecurityInternalExternal

Automatic Deployment with Watchtower

Until now, deployment required manually running:

docker-compose pull
docker-compose up -d

We can automate this using Watchtower.

What is Watchtower?

Watchtower monitors running containers and automatically updates them when a new image is available.

Install Watchtower

On the test server:

docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower \
  --interval 30
  • Checks for updates every 30 seconds

Optional: Enable per-service updates

services:
  user-service:
    image: 192.168.1.144:5000/user-service:latest
    labels:
      - "com.centurylinklabs.watchtower.enable=true"

Run Watchtower with:

--label-enable

How It Works

Dev PC → build-push.sh → push to registry

Test Server:
Watchtower detects new image
→ pulls image
→ restarts container automatically

Advantages

  • No manual deployment needed
  • Near real-time updates
  • Simple setup
  • Works without full CI/CD pipeline

Considerations

  1. Using latest may cause caching issues
    → Prefer versioned tags (e.g., :20260324-1)
  2. Containers are restarted automatically
    → Not zero-downtime by default
  3. For production:
    • Add validation
    • Add rollback strategy

Final Result

With this setup: 👉 Push from Dev PC automatically updates the test server.

Simple, fast, and cost-free deployment inside your LAN.