diff --git a/README.md b/README.md index 307543a7..ef1f0fe2 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ - **Built-in ingress** — reverse proxy with TLS termination and subdomain routing - **GPU passthrough** — vGPU and VFIO device support - **OCI image support** — pull and run standard container images +- **Remote registry push** — export cached images to any OCI registry (AWS ECR, Docker Hub, ghcr, ...) with docker-style borrowed credentials - **Remote API** — JWT-authenticated server with a separate CLI client ## Requirements @@ -103,6 +104,17 @@ Hypeman is configured via YAML config files. See [`config.example.yaml`](config.example.yaml) (Linux) and [`config.example.darwin.yaml`](config.example.darwin.yaml) (macOS) for all available server options. +To expose the API through Caddy on a public HTTPS hostname, configure the hostname and TLS in the server config. The hostname must also be included in `acme.allowed_domains`: + +```yaml +api: + hostname: api.example.com + tls: true + redirect_http: true +``` + +With this configuration, use `https://api.example.com` as the API base URL. Without it, the API is available on the server's configured port (4973 by default). + ## Usage ```bash @@ -133,6 +145,80 @@ hypeman exec my-app whoami hypeman exec -it my-app /bin/sh ``` +### Pushing Images to Remote Registries + +A ready image can be exported asynchronously to any OCI registry through the +remote API. Set the API base URL and API key used by the examples below: + +```bash +export HYPEMAN_BASE_URL="https://api.example.com" +export HYPEMAN_API_KEY="" +``` + +Credentials use the Docker model: + +- If `credentials` is provided, the client lends `username`/`password` or + `registry_token` for this push only. Hypeman uses them in memory and never + persists or logs them. +- If `credentials` is omitted, Hypeman uses the server's Docker keychain, + including `/root/.docker/config.json` or the configured service user's + `~/.docker/config.json` and any credential helpers. +- If Hypeman restarts while a push using borrowed credentials is running, that + job fails instead of retrying without the original credentials. Anonymous + interrupted jobs can be recovered with the server's keychain. + +Use an HTTPS API URL when sending credentials. The `insecure` request field +controls only the connection from Hypeman to the destination registry. + +For example, push to ECR using a short-lived login password: + +```bash +export ECR_PASSWORD="$(aws ecr get-login-password --region us-east-1)" + +curl --fail-with-body --silent --show-error \ + -X POST "$HYPEMAN_BASE_URL/pushes" \ + -H "Authorization: Bearer $HYPEMAN_API_KEY" \ + -H "Content-Type: application/json" \ + --data "{ + \"image\": \"myapp:latest\", + \"target\": \"123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:v1\", + \"credentials\": { + \"username\": \"AWS\", + \"password\": \"$ECR_PASSWORD\" + } + }" +``` + +The response contains a push `id`. Poll it until the status is `pushed` or +`failed`: + +```bash +curl --fail-with-body --silent --show-error \ + "$HYPEMAN_BASE_URL/pushes/" \ + -H "Authorization: Bearer $HYPEMAN_API_KEY" +``` + +Push jobs move through `queued`, `pushing`, and `pushed` or `failed`. +`queue_position` is present while a job is queued; successful jobs report +`layers`, `bytes`, and `completed_at`, while failed jobs report `error`. +`GET /pushes` lists jobs newest first. + +Set `"insecure": true` only when the destination registry uses plain HTTP. +HTTPS registries do not need this option. Invalid image names or targets return +`400`, a missing image returns `404`, and only images in the `ready` state can +be pushed (`409 image_not_ready` otherwise). + +Layer blobs are preserved. OCI manifest digests are preserved too, while a +Docker v2 manifest is converted to OCI and can therefore receive a new digest; +use the returned `digest` as the destination manifest digest. + +The default limit is two concurrent pushes. Increase or lower it with: + +```yaml +limits: + max_concurrent_pushes: 2 +``` + ### VM Lifecycle ```bash diff --git a/config.example.darwin.yaml b/config.example.darwin.yaml index 54787c17..31ab3ef7 100644 --- a/config.example.darwin.yaml +++ b/config.example.darwin.yaml @@ -99,6 +99,16 @@ caddy: internal_dns_port: 5354 stop_on_shutdown: false +# ============================================================================= +# Public API Ingress (optional) +# ============================================================================= +# Exposes the Hypeman API through Caddy. Leave hostname empty to use the API +# directly on port 4973 instead. +# api: +# hostname: api.example.com +# tls: true # requires the hostname in acme.allowed_domains +# redirect_http: true # redirect HTTP requests to HTTPS + # ============================================================================= # Build System Configuration # ============================================================================= @@ -124,6 +134,7 @@ build: limits: max_vcpus_per_instance: 4 max_memory_per_instance: 8GB + # max_concurrent_pushes: 2 # concurrent outbound registry pushes # max_total_volume_storage: "" # 0 or empty = unlimited # ============================================================================= diff --git a/config.example.yaml b/config.example.yaml index ca713c00..ebef4125 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -98,6 +98,16 @@ data_dir: /var/lib/hypeman # internal_dns_port: 0 # 0 = random (for dev); install script sets to 5353 for production # stop_on_shutdown: false # Set to true if you want Caddy to stop when hypeman stops +# ============================================================================= +# Public API Ingress (optional) +# ============================================================================= +# Exposes the Hypeman API through Caddy. Leave hostname empty to use the API +# directly on port 4973 instead. +# api: +# hostname: api.example.com +# tls: true # requires the hostname in acme.allowed_domains +# redirect_http: true # redirect HTTP requests to HTTPS + # ============================================================================= # TLS / ACME Configuration (for HTTPS ingresses) # ============================================================================= @@ -168,6 +178,6 @@ data_dir: /var/lib/hypeman # max_memory_per_instance: 32GB # max_total_volume_storage: "" # 0 or empty = unlimited # max_concurrent_builds: 1 -# max_concurrent_pushes: 2 +# max_concurrent_pushes: 2 # concurrent outbound registry pushes # push_timeout: 30m # max_overlay_size: 100GB