Metadata-Version: 2.4
Name: redock-vps
Version: 0.1.1
Summary: One-command Docker deployment to Hetzner VPS
License: MIT License
        
        Copyright (c) 2026 Florent Pigout
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: dnspython>=2.6
Requires-Dist: docker>=7.1
Requires-Dist: fabric>=3.2
Requires-Dist: hcloud>=2.0
Requires-Dist: pydantic-settings>=2.4
Requires-Dist: requests>=2.31
Requires-Dist: rich>=13.7
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# redock

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

```bash
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](https://www.hetzner.com/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](https://docs.astral.sh/uv/)

For automatic DNS (optional): a [Hetzner DNS](https://www.hetzner.com/dns-console) account with a zone for your domain.

## Install

```bash
uv pip install redock
```

Or from source:

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

## Configuration

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

```bash
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:

```bash
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.com](https://console.hetzner.com) → **Networking → 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](https://docs.hetzner.cloud/reference/cloud#tag/zones) (`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](https://excalidraw.com) as `drawing.apps.example.com`:

```bash
redock up excalidraw --slug drawing
```

```text
● 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:

```bash
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
```

## App catalog

```bash
# 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](https://trivy.dev) and [Docker Scout](https://docs.docker.com/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

```bash
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

```bash
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

```bash
# 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
