Automated deployment of a zot OCI registry on Hetzner Cloud with object storage backend.
This repository contains automation to deploy a self-configuring zot container registry on Hetzner Cloud infrastructure. The setup includes:
- Zot container running via Podman
- Systemd service for automatic startup and management
- Podman auto-update for automatic container updates
- Hetzner Object Storage as the storage backend (S3-compatible)
- Caddy reverse proxy for public access
- Automatic HTTPS with Let's Encrypt (zero-config SSL/TLS)
- Cloud-init based bootstrap for zero-touch deployment
- Git-based configuration sync with age encryption for secrets
.
├── config/ # Configuration files
│ ├── Caddyfile # Caddy reverse proxy config
│ ├── zot-config.json # Zot registry configuration (template)
│ └── zot.env.example # Environment configuration template
├── scripts/ # Shell scripts
│ ├── setup.sh # Interactive deployment wizard
│ ├── zot-sync-config.sh # Git-based config sync script
│ └── age-helper.sh # Age encryption helper
├── systemd/ # Systemd unit files
│ ├── caddy.service # Caddy service
│ ├── zot.service # Zot registry service
│ ├── zot-config-sync.service # Config sync service
│ └── zot-config-sync.timer # Config sync timer
├── cloud-init.yaml # Base cloud-init template
├── cloud-init-custom.yaml # Generated custom cloud-init
└── README.md # This file
Internet → Caddy (80/443) → Zot Container (5000) → Hetzner Object Storage
The registry is exposed publicly through Caddy, which acts as a reverse proxy with automatic HTTPS. Authentication is handled via htpasswd, and all image data is stored in Hetzner Object Storage.
You'll need an SSH public key to access the server. If you don't have one:
# Generate a new ED25519 key (recommended, secure)
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519_zot -C "zot-registry-admin"
# Breakdown:
# -t ed25519 Modern, secure algorithm
# -a 100 100 KDF rounds (slows brute force)
# -f ~/.ssh/... Custom output filename
# -C "..." Comment to identify key
# Or RSA 4096-bit if ED25519 is not supported
ssh-keygen -t rsa -b 4096 -a 100 -f ~/.ssh/id_rsa_zot -C "zot-registry-admin"
# Your keys will be at:
# Private: ~/.ssh/id_ed25519_zot
# Public: ~/.ssh/id_ed25519_zot.pub (use this with scripts/setup.sh)- Create a project in Hetzner Cloud Console
- Generate an API token (optional, for CLI deployment)
-
Create an Object Storage bucket:
- Go to Storage → Object Storage in Hetzner Cloud Console
- Choose your region - available regions:
fsn1- Falkenstein, Germanynbg1- Nuremberg, Germanyhel1- Helsinki, Finland
- Create a new bucket (e.g.,
my-registry-storage) - Note: Deploying your server in the same region is recommended for optimal performance and lower latency
-
Generate S3 credentials:
- Click on your bucket
- Go to "S3 Keys" section
- Create new credentials
- Save the Access Key ID and Secret Access Key
HTTPS is required for the web UI authentication to work properly.
- Register a domain name (e.g.,
cr.yourdomain.com) - Point an A record to your server's IP address
- Caddy will automatically obtain and manage SSL certificates from Let's Encrypt
-
Configure DNS (do this first):
- Create an A record pointing your domain (e.g.,
cr.yourdomain.com) to your future server IP - Or prepare to update DNS immediately after server creation
- Create an A record pointing your domain (e.g.,
-
Prepare your configuration:
- Run
./scripts/setup.shto generatecloud-init-custom.yamlwith your domain, SSH key, and credentials - The script will prompt you for your domain name and automatically configure HTTPS
- Or manually edit
cloud-init.yamland replace:80with your domain in the Caddyfile section
- Run
-
Create a new server:
- Go to Hetzner Cloud Console
- Click "Add Server"
- Select location (recommended: same region as Object Storage bucket for optimal performance - fsn1, nbg1, or hel1)
- Choose Debian 13 (Trixie)
- Select server type (CX23 recommended, 2 vCPU / 4GB RAM)
-
Configure Cloud-Init:
- Scroll to "Cloud config" section
- Paste contents of
cloud-init-custom.yaml(generated by scripts/setup.sh)
-
Create the server:
- Click "Create & Buy now"
- Note the server IP address
- Update your DNS A record if you haven't already
- Wait 2-3 minutes for the server to boot and configure
-
Access your registry:
- Wait for DNS propagation:
dig +short cr.yourdomain.com - Open
https://cr.yourdomain.comin your browser - Caddy will automatically obtain an SSL certificate (may take 30-60 seconds)
- Login with:
admin/changeme - Change the default password immediately!
- Wait for DNS propagation:
# Install hcloud CLI
brew install hcloud # macOS
# or
snap install hcloud # Linux
# Authenticate
hcloud context create my-project
# Generate customized cloud-init with your domain and credentials
scripts/setup.sh
# Create server with cloud-init
# Recommended: Use same location as Object Storage bucket for optimal performance
hcloud server create \
--name zot-registry \
--type cx23 \
--image debian-13 \
--location fsn1 \
--user-data-from-file cloud-init-custom.yaml
# Note the server IP and update your DNS A record
# Access your registry at https://your-domain.com once DNS propagatesIf you didn't modify the cloud-init file before deployment:
ssh root@YOUR_SERVER_IP
# Edit environment configuration
nano /etc/zot/zot.env
# Update with your actual values:
AWS_ACCESS_KEY_ID=your_access_key_id
AWS_SECRET_ACCESS_KEY=your_secret_access_key
BUCKET_NAME=my-registry-storage
BUCKET_REGION=eu-central
BUCKET_ENDPOINT=fsn1.your-objectstorage.com
# Restart service (config.json is auto-generated from template with these values)
systemctl restart zot.service# Generate new password
htpasswd -Bbn newuser newpassword > /etc/zot/htpasswd
# Restart service
systemctl restart zot.serviceIt is adviced to encrypt this file using age. See documentation below on how to do this.
htpasswd -bB htpasswd newuser newpassword
Caddy automatically obtains and renews SSL/TLS certificates from Let's Encrypt!
# Edit Caddyfile
nano /etc/caddy/Caddyfile
# Replace :80 with your domain name:
# Change: :80 {
# To: your-registry.example.com {
# Or uncomment the example section and modify it
# Restart Caddy
systemctl restart caddy
# That's it! Caddy automatically:
# - Obtains Let's Encrypt certificate
# - Configures HTTPS
# - Redirects HTTP to HTTPS
# - Renews certificates before expirationCheck certificate status:
# View Caddy logs
journalctl -u caddy -f
# Check if HTTPS is working
curl https://your-registry.example.com/v2/The instance includes a pull-based configuration management system that automatically syncs configs from a git repository every 5 minutes. Sensitive files can be encrypted with age for secure storage in git.
Quick Start with Helper Script:
This repository includes scripts/age-helper.sh to simplify age encryption:
# Generate key pair (on local machine or server)
scripts/age-helper.sh generate
# Encrypt a file
scripts/age-helper.sh encrypt zot.env age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# Decrypt a file (for testing)
scripts/age-helper.sh decrypt zot.env.age /path/to/age-key.txtManual Setup:
- On the server - Generate an age encryption key:
ssh root@YOUR_SERVER_IP
# Generate age key pair
age-keygen -o /etc/zot/age-key.txt
# Secure the private key
chmod 600 /etc/zot/age-key.txt
# Display the public key (save this for encrypting files)
grep "# public key:" /etc/zot/age-key.txt
# Example output: # public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p- On your local machine - Create a git repository for your configuration files:
# Install age on your local machine
# macOS: brew install age
# Linux: apt install age / dnf install age
# Windows: Download from https://github.com/FiloSottile/age/releases
mkdir zot-config
cd zot-config
git init
# Add non-sensitive configuration files (plaintext)
cp /path/to/Caddyfile .
cp /path/to/zot-config.json .
# Encrypt sensitive files with age (use the public key from step 1)
AGE_PUBLIC_KEY="age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
# Encrypt environment configuration
age -r $AGE_PUBLIC_KEY -o zot.env.age /path/to/zot.env
# Encrypt htpasswd (optional)
age -r $AGE_PUBLIC_KEY -o htpasswd.age /path/to/htpasswd
# Add files to git
git add Caddyfile zot-config.json zot.env.age htpasswd.age
# Create .gitignore to prevent committing plaintext secrets
cat > .gitignore << EOF
zot.env
htpasswd
*.key
*.pem
EOF
git add .gitignore
git commit -m "Initial configuration with encrypted secrets"
git remote add origin https://github.com/yourusername/zot-config.git
git push -u origin main- Set up git authentication on the server:
The server needs authentication to pull from your repository. Choose one method:
Option A: SSH Deploy Key (Recommended for private repos)
ssh root@YOUR_SERVER_IP
# Generate SSH key for git sync (no passphrase for automated pulling)
ssh-keygen -t ed25519 -f /root/.ssh/zot-config-deploy -N "" -C "zot-config-sync"
# Display the public key
cat /root/.ssh/zot-config-deploy.pub
# Copy this outputNow add this as a deploy key to your repository:
- GitHub: Settings → Deploy keys → Add deploy key
- GitLab: Settings → Repository → Deploy keys → Add key
- Gitea/Forgejo: Settings → Deploy Keys → Add Deploy Key
Configure git to use this key:
# Create SSH config for the deploy key
cat > /root/.ssh/config << EOF
Host github.com
IdentityFile /root/.ssh/zot-config-deploy
StrictHostKeyChecking accept-new
Host gitlab.com
IdentityFile /root/.ssh/zot-config-deploy
StrictHostKeyChecking accept-new
EOF
chmod 600 /root/.ssh/config
# Edit the config sync environment file
nano /etc/zot/zot-config-sync.env
# Use SSH URL (not HTTPS):
CONFIG_REPO_URL=ssh://git@github.com/yourusername/zot-config.git
CONFIG_BRANCH=mainOption B: HTTPS with Personal Access Token (Alternative)
Create a personal access token in your git provider:
- GitHub: Settings → Developer settings → Personal access tokens → Generate new token
- Scope:
repo(or justpublic_repofor public repos)
- Scope:
- GitLab: User Settings → Access Tokens → Add new token
- Scope:
read_repository
- Scope:
ssh root@YOUR_SERVER_IP
# Edit the config sync environment file
nano /etc/zot/zot-config-sync.env
# Use token in URL:
CONFIG_REPO_URL=https://YOUR_TOKEN@github.com/yourusername/zot-config.git
CONFIG_BRANCH=main
# Or for GitHub with username:
CONFIG_REPO_URL=https://yourusername:YOUR_TOKEN@github.com/yourusername/zot-config.git
CONFIG_BRANCH=mainOption C: Public Repository (Not recommended for configs)
ssh root@YOUR_SERVER_IP
nano /etc/zot/zot-config-sync.env
# Use HTTPS URL (no auth needed for public repos):
CONFIG_REPO_URL=https://github.com/yourusername/zot-config.git
CONFIG_BRANCH=main- Enable sync and verify:
# Restart the timer to apply changes
systemctl restart zot-config-sync.timer
# Check timer status
systemctl status zot-config-sync.timer
# Test the connection by manually triggering sync
systemctl start zot-config-sync.service
# View sync logs (look for "Configuration sync completed successfully")
journalctl -u zot-config-sync -n 50
# For live monitoring:
journalctl -u zot-config-sync -fCommon authentication issues:
- Permission denied (publickey): Deploy key not added to repository or SSH config incorrect
- Authentication failed: Wrong token or token doesn't have correct permissions
- Could not resolve host: Check network connectivity and DNS
- Repository not found: Check repository URL and access permissions
Usage Workflow:
# On your local machine:
cd zot-config
# Update non-sensitive files (plaintext)
nano Caddyfile
git add Caddyfile
git commit -m "Update reverse proxy settings"
git push origin main
# Update sensitive files (encrypted)
# 1. Edit the plaintext file
nano zot.env
# 2. Re-encrypt with age (use same public key)
AGE_PUBLIC_KEY="age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
age -r $AGE_PUBLIC_KEY -o zot.env.age zot.env
# 3. Commit and push the encrypted version
git add zot.env.age
git commit -m "Update environment configuration"
git push origin main
# The instance will automatically:
# 1. Pull changes within 5 minutes
# 2. Detect which files changed (.age or plaintext)
# 3. Decrypt .age files using the server's private key
# 4. Apply configs to /etc/caddy or /etc/zot
# 5. Reload/restart affected servicesMonitored Files:
Caddyfile→/etc/caddy/Caddyfile(validates before applying, reloads Caddy)zot-config.json→/etc/zot/config.json.template(template for env var substitution, restarts zot service)zot.envorzot.env.age→/etc/zot/zot.env(decrypts if .age, contains all config including bucket settings, restarts zot service)htpasswdorhtpasswd.age→/etc/zot/htpasswd(decrypts if .age, restarts zot service)
Security Notes:
- age encryption is built-in - use
.ageextension for encrypted files (recommended for zot.env and htpasswd) - The server's private key is stored at
/etc/zot/age-key.txt(protected with 600 permissions) - Only the server can decrypt
.agefiles - they're safe to commit to git - Use a private repository for configuration storage, or public with encrypted secrets
- For public repos, use deploy keys with read-only access
- The sync script validates Caddyfile syntax before applying changes
- Never commit plaintext zot.env - always use
.ageencrypted versions
# Log in to your registry
podman login your-domain.com
# or
docker login your-domain.com
# Tag an image
podman tag myimage:latest your-domain.com/myimage:latest
# Push the image
podman push your-domain.com/myimage:latestpodman pull your-domain.com/myimage:latestsystemctl status zot.service# Service logs
journalctl -u zot.service -f
# Application logs
tail -f /var/log/zot/zot.log
# Audit logs
tail -f /var/log/zot/audit.logsystemctl restart zot.serviceThe container is configured to automatically check for updates daily at midnight using podman's auto-update feature.
# Check auto-update timer status
systemctl status podman-auto-update.timer
# View last auto-update run
systemctl status podman-auto-update.service
# Manually check for updates (dry-run)
podman auto-update --dry-run
# Manually trigger update
podman auto-update
# View auto-update logs
journalctl -u podman-auto-update.serviceThe container has the io.containers.autoupdate=registry label, which means podman will:
- Check the container registry for image updates
- Pull the new image if available
- Restart the container with the new image
- Rollback to the previous version if the restart fails
If you enabled git-based configuration sync, you can manage it with these commands:
# Check sync timer status
systemctl status zot-config-sync.timer
# View sync logs (live)
journalctl -u zot-config-sync -f
# View recent sync history
journalctl -u zot-config-sync -n 50
# Manually trigger sync (useful for testing)
systemctl start zot-config-sync.service
# Disable automatic sync
systemctl stop zot-config-sync.timer
systemctl disable zot-config-sync.timer
# Re-enable automatic sync
systemctl enable zot-config-sync.timer
systemctl start zot-config-sync.timer
# Change sync repository
nano /etc/zot/zot-config-sync.env
systemctl restart zot-config-sync.timerSync behavior:
- Runs every 5 minutes automatically
- Only applies changes if git repository has updates
- Validates Caddyfile syntax before applying
- Automatically reloads/restarts affected services
- Logs all actions to journald
To update Caddy to the latest version:
# Download latest binary
curl -L "https://github.com/caddyserver/caddy/releases/latest/download/caddy_linux_amd64.tar.gz" -o /tmp/caddy.tar.gz
# Extract and replace
tar -xzf /tmp/caddy.tar.gz -C /tmp
mv /tmp/caddy /usr/local/bin/caddy
chmod +x /usr/local/bin/caddy
rm /tmp/caddy.tar.gz
# Restart Caddy
systemctl restart caddy
# Verify version
/usr/local/bin/caddy versionOr use the built-in upgrade command (requires existing Caddy installation):
caddy upgrade
systemctl restart caddyAll configuration templates are located in the config/ directory, and systemd units in systemd/.
Template configuration file for zot with S3 storage backend using environment variable placeholders. Key sections:
- storage.storageDriver: S3 backend configuration (uses
${BUCKET_NAME},${BUCKET_REGION},${BUCKET_ENDPOINT}from zot.env) - http.auth: Authentication settings (htpasswd)
- http.accessControl: Repository access policies
- log: Logging configuration
This file is deployed as /etc/zot/config.json.template and processed with envsubst at service start to generate the final /etc/zot/config.json.
Environment configuration template containing all deployment-specific settings:
- AWS S3 credentials: Access key and secret key for Object Storage
- Bucket configuration: Bucket name, region, and endpoint
- Variables are substituted into config.json at runtime
- Copy to
zot.envand customize for your deployment - Encrypt with age before committing to git
Systemd service unit file that:
- Manages the zot Podman container lifecycle
- Automatically restarts on failure
- Uses Type=notify for proper systemd integration
- Runs envsubst to generate config.json from template with environment variables
- Configures container with auto-update label
- Loads all configuration from
/etc/zot/zot.env - Includes SELinux labels (Z) for volume mounts
Single source of truth for Caddy configuration.
This file:
- Configures reverse proxy to zot container
- Handles automatic HTTPS with Let's Encrypt
- Sets proper headers for container registry
- Configures unlimited request buffers for large uploads
- Sets appropriate timeouts for image operations
- Enables access logging
Important: This file is deployed via cloud-init's write_files section. Edit this file and regenerate cloud-init with scripts/setup.sh to apply changes.
Systemd service unit for Caddy:
- Runs Caddy as caddy user/group
- Type=notify for proper systemd integration
- Automatic reload support
- Security hardening (ProtectSystem, PrivateTmp)
- CAP_NET_BIND_SERVICE capability for binding to privileged ports
Bootstrap script that:
- Uses
write_filesto deploy Caddyfile from repository - Downloads Caddy and age binaries from official GitHub releases
- Installs Podman and utilities
- Creates Caddy user and directory structure
- Configures zot and systemd services
- Enables podman auto-update timer
- Enables git-based configuration sync timer
- Sets up firewall rules
- Starts all services
Note: The Caddyfile content comes from the separate config/Caddyfile file via write_files section.
Git-based configuration sync script that:
- Pulls configuration updates from a git repository every 5 minutes
- Detects which files have changed
- Decrypts age-encrypted files (
.ageextension) using server's private key - Validates Caddyfile syntax before applying
- Copies updated configs to appropriate locations
- Automatically reloads/restarts affected services
- Logs all sync operations to journald
Configuration:
- Set
CONFIG_REPO_URLin/etc/zot/zot-config-sync.env - Age private key stored at
/etc/zot/age-key.txt(auto-generated or manual) - Supports both plaintext and
.ageencrypted files (encrypted preferred for secrets)
Systemd service unit for configuration sync:
- Type=oneshot for one-time execution per trigger
- Loads git repository URL from environment file
- Runs the sync script with proper permissions
- Security hardening with ProtectSystem and ReadWritePaths
Systemd timer for periodic config sync:
- Triggers every 5 minutes (configurable)
- Runs 1 minute after boot
- Persistent across reboots
- Enables pull-based GitOps workflow
- Change default passwords immediately after deployment
- Use SSL/TLS for production deployments
- Restrict firewall rules to necessary ports only
- Secure S3 credentials - never commit them to git
- Automatic updates - podman auto-update keeps zot container current
- System updates - keep OS and packages updated with unattended-upgrades
- Access control - configure appropriate user permissions in config.json
- Network security - use Hetzner Cloud Firewalls for additional protection
- Caddy and age binaries - downloaded directly from official GitHub releases (no third-party repos)
- Minimal dependencies - fewer packages means smaller attack surface
- Git configuration sync - use private repos or deploy keys; age encryption built-in for secrets (
.agefiles)
# Check service status
systemctl status zot.service
# Check Podman logs
podman logs zot
# Check container status
podman ps -a
# Verify S3 credentials and bucket config
cat /etc/zot/zot.env
# Test S3 connectivity
podman run --rm \
-e AWS_ACCESS_KEY_ID=your_key \
-e AWS_SECRET_ACCESS_KEY=your_secret \
amazon/aws-cli \
s3 ls --endpoint-url=https://your-bucket.fsn1.your-objectstorage.com# Check if Caddy is running
systemctl status caddy
# Check Caddy logs
journalctl -u caddy -n 50
# Check if zot container is running
podman ps | grep zot
# Test local connectivity to zot
curl http://localhost:5000/v2/
# Test through Caddy
curl http://localhost:80/v2/
# Check firewall
ufw status
# Validate Caddyfile syntax
caddy validate --config /etc/caddy/Caddyfile# Check timer is active
systemctl status podman-auto-update.timer
# Check last run
journalctl -u podman-auto-update.service -n 50
# Verify container label
podman inspect zot | grep autoupdate
# Manually trigger update
podman auto-update# Check Caddy logs for certificate errors
journalctl -u caddy -n 100 | grep -i certificate
# Verify domain points to your server
dig +short your-domain.com
# Check if ports are accessible
curl -v http://your-domain.com
curl -v https://your-domain.com
# Force certificate renewal
caddy reload --config /etc/caddy/CaddyfileCommon issues:
- DNS not pointing to server IP (wait for DNS propagation)
- Port 80/443 blocked by firewall
- Domain validation failing (ensure ports are open)
- Rate limits from Let's Encrypt (use staging environment for testing)
- Verify bucket name and region are correct
- Check that S3 credentials have proper permissions
- Ensure bucket endpoint URL matches your region
- Test credentials with aws-cli or s3cmd
# Check if timer is running
systemctl status zot-config-sync.timer
# Check sync service logs
journalctl -u zot-config-sync -n 100
# Verify git repository is configured
cat /etc/zot/zot-config-sync.env
# Test git connectivity
cd /opt/zot-config
git fetch origin
# Manually trigger sync to see errors
systemctl start zot-config-sync.service
journalctl -u zot-config-sync -fCommon issues:
CONFIG_REPO_URLnot set in/etc/zot/zot-config-sync.env- Git repository requires authentication (use deploy keys or HTTPS with tokens)
- Network connectivity issues (check firewall, DNS)
- Repository is empty or missing expected config files
- File permissions preventing config file updates
- Caddyfile syntax errors (validation will fail and skip update)
- Age decryption failures (wrong key, corrupted .age file, age not installed)
- Missing age key at
/etc/zot/age-key.txt
Age decryption troubleshooting:
# Verify age is installed
age --version
# Check if age key exists
ls -la /etc/zot/age-key.txt
# View the public key
grep "# public key:" /etc/zot/age-key.txt
# Test decryption manually
age -d -i /etc/zot/age-key.txt /opt/zot-config/zot.env.age
# Re-encrypt file if needed (on local machine)
AGE_PUBLIC_KEY="age1..." # Get from server
age -r $AGE_PUBLIC_KEY -o zot.env.age zot.env
# Verify encrypted file format
file /opt/zot-config/zot.env.age
# Should show: ASCII text (age-encrypted data)Setting up authentication for private repos:
For HTTPS with token:
# Edit the environment file
nano /etc/zot/zot-config-sync.env
# Use token in URL
CONFIG_REPO_URL=https://YOUR_TOKEN@github.com/yourusername/zot-config.gitFor SSH deploy keys:
# Generate SSH key on server
ssh-keygen -t ed25519 -f /root/.ssh/zot-config-deploy -N ""
# Add public key to your git repository as a deploy key
cat /root/.ssh/zot-config-deploy.pub
# Configure git to use the key
cat > /root/.ssh/config << EOF
Host github.com
IdentityFile /root/.ssh/zot-config-deploy
StrictHostKeyChecking accept-new
EOF
# Use SSH URL in config
nano /etc/zot/zot-config-sync.env
CONFIG_REPO_URL=ssh://git@github.com/yourusername/zot-config.gitExample monthly costs for Hetzner:
- CX23 server (2 vCPU, 4GB RAM): ~€6.39/month
- Object Storage: €0.0049/GB/month + €0.01/GB transfer
- Traffic: 20TB included with server
Total: ~€7-12/month for small to medium usage
The default configuration uses Hetzner Object Storage:
{
"storage": {
"storageDriver": {
"name": "s3",
"region": "eu-central",
"bucket": "my-registry-storage",
"regionendpoint": "fsn1.your-objectstorage.com"
}
}
}Available Hetzner regions:
fsn1.your-objectstorage.com- Falkenstein, Germanynbg1.your-objectstorage.com- Nuremberg, Germanyhel1.your-objectstorage.com- Helsinki, Finland
For other S3-compatible storage (e.g., AWS S3):
{
"storage": {
"storageDriver": {
"name": "s3",
"region": "us-east-1",
"bucket": "my-bucket",
"regionendpoint": "s3.amazonaws.com"
}
}
}For production deployments:
- Use multiple zot instances behind a load balancer
- Share the same S3 bucket across instances
- Use Hetzner Load Balancer for distribution
- Configure health checks on
/v2/endpoint
Add monitoring with Prometheus:
{
"extensions": {
"metrics": {
"enable": true,
"prometheus": {
"path": "/metrics"
}
}
}
}- Zot Documentation
- Zot GitHub Repository
- Hetzner Cloud Docs
- Hetzner Object Storage
- OCI Distribution Spec
This deployment automation is provided as-is for use with the zot registry project.
For issues with:
- This deployment: Open an issue in this repository
- Zot itself: Visit project-zot/zot
- Hetzner services: Contact Hetzner Support
