Deployment Guide
Docker Compose (Recommended)
Option A: From Source (with clone)
git clone git@github.com:edsuwarna/anjungan.git
cd anjungan
cp .env.example .env
docker compose up -d
This builds images locally from the source code.
Option B: Without Clone (pre-built images)
Deploy directly using pre-built images from the Zot registry — no need to clone the repo.
Create docker-compose.yml on your server:
services:
postgres:
image: postgres:17-alpine
container_name: anjungan-postgres
environment:
POSTGRES_USER: ${POSTGRES_USER:-anjungan}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-anjungan}
POSTGRES_DB: ${POSTGRES_DB:-anjungan}
ports:
- "5433:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-anjungan}"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
container_name: anjungan-redis
ports:
- "6379:6379"
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
backend:
image: registry.edsuwarna.xyz/anjungan-backend:main-latest
container_name: anjungan-backend
environment:
SERVER_HOST: 0.0.0.0
SERVER_PORT: 8080
POSTGRES_HOST: postgres
POSTGRES_USER: ${POSTGRES_USER:-anjungan}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-anjungan}
POSTGRES_DB: ${POSTGRES_DB:-anjungan}
REDIS_HOST: redis
JWT_SECRET: ${JWT_SECRET:-change-me-in-production}
GITHUB_TOKEN: ${GITHUB_TOKEN:-}
REGISTRY_URL: ${REGISTRY_URL:-http://zot:5000}
REGISTRY_EXTERNAL_URL: ${REGISTRY_EXTERNAL_URL:-registry.anjungan.io}
ZOT_ADMIN_USER: ${ZOT_ADMIN_USER:-admin}
ZOT_ADMIN_PASS: ${ZOT_ADMIN_PASS:-z0t_4dm1n_p4ss}
ZOT_HTPASSWD_PATH: ${ZOT_HTPASSWD_PATH:-/data/zot/htpasswd}
ZOT_CONTAINER_NAME: ${ZOT_CONTAINER_NAME:-anjungan-zot}
LOG_LEVEL: ${LOG_LEVEL:-info}
MIGRATIONS_PATH: /migrations
# Self-server — auto-registers the host for container/metrics visibility
SELF_SERVER_ENABLED: "true"
SELF_HOST_NETWORK: "host.docker.internal"
extra_hosts:
- "host.docker.internal:host-gateway"
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
group_add:
- "988" # Docker group GID — gives container access to /var/run/docker.sock
volumes:
- sshkeys:/data/ssh
- ./zot:/data/zot:rw
- /var/run/docker.sock:/var/run/docker.sock:rw
frontend:
image: registry.edsuwarna.xyz/anjungan-frontend:main-latest
container_name: anjungan-frontend
ports:
- "80:80"
depends_on:
- backend
zot:
image: ghcr.io/project-zot/zot-linux-amd64:latest
container_name: anjungan-zot
expose:
- "5000"
volumes:
- zotdata:/var/lib/zot
- ./zot/config.json:/etc/zot/config.json:ro
- ./zot/htpasswd:/etc/zot/htpasswd:ro
command:
- serve
- /etc/zot/config.json
restart: unless-stopped
volumes:
pgdata:
redisdata:
sshkeys:
zotdata:
Create the required Zot config:
mkdir -p zot
cat > zot/config.json << 'EOF'
{
"storage": {
"rootDirectory": "/var/lib/zot",
"gc": true,
"gcDelay": "168h",
"gcInterval": "24h"
},
"http": {
"address": "0.0.0.0",
"port": "5000",
"auth": {
"htpasswd": {
"path": "/etc/zot/htpasswd"
}
},
"accessControl": {
"repositories": {
"**": {
"policies": [
{"users": ["**"], "actions": ["read"]},
{"users": ["deploy"], "actions": ["read", "create"]},
{"users": ["admin"], "actions": ["read", "create", "update", "delete"]}
]
}
}
}
},
"log": {"level": "info"}
}
EOF
touch zot/htpasswd # auto-generated if missing (gitignored — backend syncZotHtpasswd() populates it on first run)
Create .env and start:
cat > .env << 'EOF'
JWT_SECRET=your-strong-secret-here
POSTGRES_PASSWORD=your-db-password
EOF
docker compose up -d
> Login to the registry first to pull private images:
> ```bash
> docker login registry.edsuwarna.xyz -u deploy
> ```
> You'll need registry credentials configured. Contact your admin for access.
This boots up: PostgreSQL, Redis, Backend, Frontend, and Zot (registry).
Services
| Service | Container Name | Host Port | Purpose |
|---|---|---|---|
postgres |
anjungan-postgres |
5433 | Primary database |
redis |
anjungan-redis |
6379 | Caching, rate limiting |
backend |
anjungan-backend |
8080 | Go API |
frontend |
anjungan-frontend |
80 | SPA (nginx) |
zot |
anjungan-zot |
— | OCI registry (internal) |
> Note: PostgreSQL is mapped to port 5433 externally to avoid conflicts with local PostgreSQL on port 5432.
Persistent Volumes
| Volume | Mount | Contents |
|---|---|---|
pgdata |
PostgreSQL | Database files |
redisdata |
Redis | Cache data |
sshkeys |
Backend | SSH host keys |
zotdata |
Zot | Container images |
Quick Commands
# Start
docker compose up -d
# Stop
docker compose down
# Restart a single service
docker compose restart backend
# View logs
docker compose logs -f
# Rebuild and restart (after code changes)
docker compose build && docker compose up -d
Production Considerations
1. Environment Variables
Set these in production (never use defaults):
JWT_SECRET=
POSTGRES_PASSWORD=
GITHUB_TOKEN=
2. Security
ZOT_ADMIN_PASS from the default3. Zot Registry
Zot runs as an internal-only service in Docker. For external access, configure your reverse proxy to route /v2/* to zot:5000 with basic auth.
See docs/registry.md for self-service credential management.
4. Database Migrations
Migrations auto-run on backend startup. To trigger manually:
docker compose restart backend
5. Backups
Back up the PostgreSQL volume and zot/ directory regularly.
6. Self-Server (Host Auto-Registration)
On startup, Anjungan can auto-detect and register the host server where it runs — no manual add needed. See docs/self-server.md for details.
To enable, ensure your docker-compose.yml has:
backend:
environment:
SELF_SERVER_ENABLED: "true"
SELF_HOST_NETWORK: "host.docker.internal"
group_add:
- "988" # docker GID — run `getent group docker | cut -d: -f3` to verify
volumes:
- /var/run/docker.sock:/var/run/docker.sock:rw
CI/CD
The project uses GitHub Actions for Docker builds:
main → builds & pushes images with main-latest + main-{sha} tagsv* → builds & pushes with release-latest + version tagsImages are pushed to the Zot registry at registry.edsuwarna.xyz.