Skip to main content

One-command Docker deployment to Hetzner VPS

Project description

redock

One command from zero to a live HTTPS app on a Hetzner VPS.

redock up excalidraw --slug drawing

That's it. redock provisions a server, installs Docker and a Caddy reverse proxy, handles DNS, deploys the container, and hands you back a live URL.

How it works

  1. Provision — finds or creates a Hetzner cx22 (or your chosen type)
  2. Prepare — SSHes in, installs Docker + caddy-docker-proxy via Docker Compose
  3. DNS — creates the A record automatically (Hetzner DNS) or prints it for manual entry, then polls until it resolves
  4. Deploy — runs the container with Caddy labels; Caddy gets a Let's Encrypt cert automatically
  5. Verify — polls HTTPS until the app responds 200, then prints the URL

The VPS is shared across deployments — each app is one container behind Caddy.

Prerequisites

  • A Hetzner Cloud account and API token
  • An SSH key uploaded to Hetzner (default name: redock-key)
  • A domain you can add DNS records to
  • Python 3.11+ and uv

For automatic DNS (optional): a Hetzner DNS account with a zone for your domain.

Install

uv pip install redock

Or from source:

git clone https://gitlab.com/toopy/redock
cd redock
uv sync

Configuration

Copy .env.example to .env and fill in the required values:

cp .env.example .env
Variable Required Default Description
REDOCK_HETZNER_TOKEN yes Hetzner Cloud API token
REDOCK_BASE_DOMAIN yes Domain for deployments, e.g. apps.example.com
REDOCK_ACME_EMAIL yes Email for Let's Encrypt notifications
REDOCK_HETZNER_SSH_KEY_NAME no redock-key SSH key name in Hetzner Cloud
REDOCK_SSH_KEY_PATH no ~/.ssh/id_ed25519 Path to local private key
REDOCK_HETZNER_REGION no fsn1 Hetzner datacenter
REDOCK_HETZNER_SERVER_TYPE no cx22 Server type
REDOCK_STATE_FILE no ~/.redock/state.json Where deployment state is stored
REDOCK_CATALOG_DB no ~/.redock/catalog.db Catalog SQLite database path
REDOCK_INFOMANIAK_API_KEY no Required for catalog update --use-ai
REDOCK_INFOMANIAK_PRODUCT_ID no Required for catalog update --use-ai
REDOCK_INFOMANIAK_CHAT_MODEL no google/gemma-4-31B-it LLM model for metadata extraction
REDOCK_DOCKERHUB_USERNAME no Docker Hub username for private image metadata
REDOCK_DOCKERHUB_TOKEN no Docker Hub access token (read-only) for private image metadata
REDOCK_SCAN_TRIVY_WEIGHT no 0.5 Trivy's share of the combined scan score
REDOCK_SCAN_SCOUT_WEIGHT no 0.5 Docker Scout's share of the combined scan score
REDOCK_SCAN_SEV_CRITICAL no 5 Penalty coefficient per CRITICAL vulnerability
REDOCK_SCAN_SEV_HIGH no 2 Penalty coefficient per HIGH vulnerability
REDOCK_SCAN_SEV_MEDIUM no 0.5 Penalty coefficient per MEDIUM vulnerability
REDOCK_SCAN_SEV_LOW no 0.1 Penalty coefficient per LOW vulnerability

Then verify everything looks good:

redock doctor

Automatic DNS with Hetzner DNS

redock uses REDOCK_HETZNER_TOKEN for both VPS provisioning and DNS management. If your domain zone exists in console.hetzner.comNetworking → DNS, redock will create or update the A record automatically on every redock up.

Setup: Add your domain zone in the Hetzner Console and point your registrar's nameservers to Hetzner's — no extra token needed.

Note: redock uses the Hetzner Cloud DNS API (api.hetzner.cloud/v1). The zone must already exist — redock manages records within a zone, not the zone itself. It discovers the right zone by walking the hostname labels (e.g. for drawing.apps.example.com it tries apps.example.com, then example.com).

A real example

Deploy Excalidraw as drawing.apps.example.com:

redock up excalidraw --slug drawing
● Ensuring VPS
● Preparing host (Docker + Caddy)
● DNS records — creating and waiting for propagation
✓ Created DNS A record: drawing.apps.example.com → 65.21.104.33
● Deploying container
● Waiting for HTTPS
✓ Live: https://drawing.apps.example.com

Done! https://drawing.apps.example.com

The --slug flag sets the subdomain prefix. If omitted it defaults to the template name:

redock up <id>                          # → <id>.<base_domain>
redock up <id> -s <slug>               # → <slug>.<base_domain>
redock up <id> --env KEY=VALUE         # override an env var at deploy time
redock up <id> --upgrade               # stop existing container, pull latest image, redeploy

Multi-container apps — docker swarm stacks

For apps that ship as more than one container (a service + a worker, an API + a frontend, anything you'd model as a docker-compose.yml), redock can deploy the whole stack via docker swarm. The same VPS hosts multiple stacks side-by-side, all behind the shared Caddy.

redock stack up ./stack.yml --slug myapp
# → https://myapp.<base_domain>

What it does:

  1. Ensure VPS + prepare host — same as redock up, but prepare also runs docker swarm init (idempotent) and ensures redock_net is an overlay --attachable network so swarm services AND the Caddy container can both attach to it.
  2. DNS — same automatic A record creation as single-container deploys.
  3. Deploy stack — uploads your YAML to /opt/redock/stacks/<slug>.yml and runs docker stack deploy -c <file> <slug> over SSH. Re-running updates the services in place (docker's native rolling update).
  4. Wait HTTPS — polls until the public URL responds.

Stack file requirements

Your stack.yml is a normal docker compose v3+ file with two additions:

  • The publicly-reachable service must declare the Caddy labels so caddy-docker-proxy routes the hostname to it:

    services:
      web:
        image: yourorg/web:latest
        networks:
          - redock_net
        deploy:
          labels:
            - "caddy=$DOMAIN"
            - "caddy.reverse_proxy={{upstreams 8080}}"
    
  • All services that must be on the Caddy network reference the external attachable overlay:

    networks:
      redock_net:
        external: true
    

$DOMAIN (and any other ${VAR}) get interpolated from --env KEY=VAL at deploy time. redock substitutes them via the shell so compose's native interpolation picks them up. See swarm-me's stack.yml for a real 4-service example.

Stack commands

redock stack up ./stack.yml --slug myapp           # deploy / update
redock stack up ./stack.yml --slug myapp \
  --env DOMAIN=myapp.deploy-me.eu                  # env substitution
redock stack up ./stack.yml --slug myapp \
  --registry-auth 'registry.gitlab.com::ci-token::glpat-xxx'  # private registry pull
redock stack rm myapp                              # take it down (keeps VPS)
redock stack list                                  # what's running on the shared VPS

Stacks coexist with single-container redock up deployments on the same VPS — Caddy reads labels from both swarm service tasks and plain containers via the shared redock_net overlay.

App catalog

# Add an app (all flags optional; Sub-project 2 will fill them via AI)
redock catalog add nginx:latest --port 80
redock catalog add louislam/uptime-kuma:latest --port 3001 --volume /app/data

# Store environment variables on the catalog item (repeatable)
redock catalog add myapp:latest --port 8080 --env DB_URL=postgres://localhost/db --env DEBUG=false
redock catalog update myapp --env SECRET_KEY=abc123   # merges with existing env vars

# Reference a host env var — prefix value with $ (use single quotes to prevent shell expansion)
redock catalog update myapp --env 'DB_PASS=$MY_SECRET'   # stored as-is; resolved at deploy time

# Mark as ready to deploy (or uncheck with --no)
redock catalog check nginx
redock catalog check --no nginx

# List, inspect, update, remove
redock catalog list
redock catalog list --checked-only
redock catalog show nginx
redock catalog update nginx --port 8080
redock catalog delete nginx

# Search the local catalog
redock catalog search ngi

# Scan an image for vulnerabilities (Trivy + Docker Scout)
redock catalog scan nginx
redock catalog scan nginx --dry-run   # scan without saving results
redock catalog scan nginx --verbose   # show per-scanner breakdown

Security scanning

redock catalog scan runs Trivy and Docker Scout against the image, merges their findings, and computes a weighted 0–100 safety score. At least one scanner must be installed — the command skips unavailable scanners gracefully and only fails if none can run.

Install Trivy

task trivy:install

This runs the official install script and places trivy in ~/.local/bin (no sudo needed). Alternatively, install manually:

  • macOS: brew install aquasecurity/trivy/trivy
  • Linux: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b ~/.local/bin

Verify: trivy --version

Install Docker Scout

task scout:install

This installs the Docker Scout CLI plugin to ~/.docker/cli-plugins. Docker Desktop 4.17+ already bundles it — run docker scout version to check before installing.

Verify: docker scout version

Note: redock up requires the template to exist in the catalog and be marked as checked. A missing port also blocks deployment.

Other commands

# See all running deployments
redock list

# Stop a deployment (keeps container and state, can be restarted)
redock stop <slug>

# Remove container + volume + state + DNS record, destroy VPS if no apps remain
redock purge <slug>
redock purge <slug> --yes  # skip confirmation

# VPS - Step-by-step control
redock vps create         # provision VPS only
redock vps install        # install Docker + Caddy only
redock vps show           # print VPS IP and status
redock vps delete         # destroy VPS (blocked if deployments still exist)

# DNS - Step-by-step control
redock dns show <slug>    # print required DNS record
redock dns update <slug>  # create/update A record (Hetzner DNS)
redock dns wait <slug>    # wait until DNS resolves
redock dns clean <slug>   # delete A record (Hetzner DNS)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

redock_vps-0.1.2.tar.gz (96.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

redock_vps-0.1.2-py3-none-any.whl (59.1 kB view details)

Uploaded Python 3

File details

Details for the file redock_vps-0.1.2.tar.gz.

File metadata

  • Download URL: redock_vps-0.1.2.tar.gz
  • Upload date:
  • Size: 96.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for redock_vps-0.1.2.tar.gz
Algorithm Hash digest
SHA256 413de4df1283d67a0709c26fba1f0d26e1a275647b063b090d652cb816105462
MD5 a0a76048c6463c91a155d8321cc30ee2
BLAKE2b-256 53ff70e05542d17b685692fffb4020c379d31a11d11014730b6edd3b9a52bfe3

See more details on using hashes here.

File details

Details for the file redock_vps-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: redock_vps-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 59.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.17 {"installer":{"name":"uv","version":"0.11.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for redock_vps-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1df37bdb99eea490c959a3b342fb9a58d4560c9e51e3cdc842e858c4db84e02b
MD5 8f54e4d8cf8cc928130d04608fb293f5
BLAKE2b-256 d424191503a55a73df7fdba98acc80e74b75cb21af54fe21af9912f24cf0fab6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page