upscayl-docker-worker
by yellowcooln
README.md
# Upscayl Docker Worker
A private, authenticated, GPU-backed [Upscayl](https://github.com/upscayl/upscayl)
worker with a local [Hermes Agent](https://github.com/NousResearch/hermes-agent)
MCP bridge. The worker runs the standalone NCNN/Vulkan backend in Docker;
Hermes runs the bridge locally so private image files do not pass through model
context.
For a complete worker-to-Hermes installation, follow
[`HOWTO.md`](HOWTO.md). It covers host preparation, secure configuration,
deployment, MCP registration, end-to-end GPU verification, and troubleshooting.
```text
Hermes -> local stdio MCP bridge -> authenticated worker API -> NVIDIA/Vulkan
<- validated local PNG <- validated job result <- Upscayl NCNN
```
This repository intentionally contains both halves:
- `upscayl_worker/` — the FastAPI service packaged in the Docker image.
- `upscayl_bridge/` — the local stdio MCP bridge installed on the Hermes host.
A separate MCP repository is not needed. The bridge and worker share one API
contract, test suite, and release lifecycle, while still running in different
places. Splitting them would add version coordination without improving the
security boundary.
## Published image
The production image is published at:
```text
yellowcooln/upscayl-docker-worker
```
Supported platform: `linux/amd64`.
GitHub Actions publishes:
- `latest` from the default branch
- branch tags such as `main` and `dev`
- version tags such as `v1.0.0`
- source-reference tags such as `sha-0883691`
Registry tags, including `sha-*`, can be overwritten. Only a registry digest is
an immutable deployment reference.
The image is built from the official Upscayl Flatpak on a hosted runner. The
backend and models remain absent from Git, while their Flatpak version and
commit are recorded inside the image at:
```text
/usr/share/doc/upscayl-docker-worker/upscayl-source.txt
```
## What is included
- Pre-parser bearer authentication and streamed upload limits
- Decoded pixel, dimension, output, intermediate, timeout, queue, and disk limits
- One active GPU worker with bounded pending admission
- Automatic photo/digital routing and exact 1×–16× output planning
- Expiring opaque job storage and restart orphan cleanup
- A narrow MCP surface: `upscale_image`, `list_models`, and `worker_status`
- Real API, MCP, PNG/CRC, and GPU-utilization verification scripts
## Worker prerequisites
- Linux x86-64 Docker host with an NVIDIA GPU
- NVIDIA driver and NVIDIA Container Toolkit
- Docker Engine; Docker Compose is optional
- Python 3 for fixture generation and independent PNG validation
Confirm basic GPU visibility:
```bash
docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 \
nvidia-smi --query-gpu=name,driver_version --format=csv,noheader
```
`nvidia-smi` alone is not proof that Upscayl works. NCNN also requires a
functional Vulkan ICD inside the container.
## Deploy the published image
Clone the repository for the hardened deployment scripts and configuration:
```bash
git clone https://github.com/yellowcooln/upscayl-docker-worker.git
cd upscayl-docker-worker
cp .env.example .env
chmod 600 .env
```
Set a random `UPSCAYL_WORKER_TOKEN`, the correct NVIDIA driver version, and the
private bind address in `.env`. The worker and deployment script reject the
public placeholder token from `.env.example`. Then deploy and verify:
```bash
scripts/deploy-api.sh
scripts/verify-api.sh
```
`deploy-api.sh` pulls `yellowcooln/upscayl-docker-worker:latest` by default,
validates that it is `linux/amd64`, and only then recreates the service. It
prints the deployed image ID and repository digest.
Docker Compose provides the same image-first deployment:
```bash
docker compose up -d
```
For reproducible production deployment, set `UPSCAYL_IMAGE` to a version tag or
immutable registry digest instead of `latest`:
```text
UPSCAYL_IMAGE=yellowcooln/upscayl-docker-worker@sha256:<digest>
UPSCAYL_PULL_POLICY=always
```
The API binds to loopback by default. Plain HTTP is appropriate only on
loopback or an encrypted trusted network such as a VPN; use HTTPS for any other
path and never publish the worker directly to the Internet.
## Verify direct GPU inference
The direct backend verifier now uses the published image rather than rebuilding
locally:
```bash
scripts/verify-gpu.sh
```
It generates a deterministic 512×384 fixture, invokes the container's
`upscayl-bin` entrypoint directly, validates the 2048×1536 output structure and
CRCs, and prints hashes.
For a manual invocation, place an image under `io/` and run:
```bash
scripts/run-worker.sh \
-i /work/input.png \
-o /work/output.png \
-m /opt/upscayl/models \
-n high-fidelity-4x \
-z 4 \
-s 4 \
-f png \
-v
```
## Hermes MCP bridge
The bridge must run on the Hermes host because that is where local attachments
and filesystem paths exist. It should not run inside the remote GPU container.
Install it from this same checkout:
```bash
uv sync --frozen --no-dev --extra bridge
```
The bridge uses these environment-backed settings:
```text
UPSCAYL_WORKER_URL=http://private-gpu-host:8788
UPSCAYL_WORKER_TOKEN=<secret>
UPSCAYL_ALLOW_INSECURE_HTTP=true
UPSCAYL_ALLOWED_INPUT_ROOTS=/home/user/.hermes:/home/user/Pictures
UPSCAYL_OUTPUT_DIR=/home/user/Pictures/upscayl
```
Non-loopback HTTP requires the explicit `UPSCAYL_ALLOW_INSECURE_HTTP=true`
acknowledgement; omit it when using HTTPS or loopback. `UPSCAYL_ALLOWED_INPUT_ROOTS`
is required; the bridge fails closed instead of
defaulting to the whole home directory. Store these settings in the active
Hermes profile's mode-0600 `.env`. Register the bridge without copying the token
into `config.yaml` or process arguments:
```bash
hermes mcp add upscayl \
--command /usr/bin/bash \
--connect-timeout 60 \
--args -lc 'set -a; source "${HERMES_HOME:-$HOME/.hermes}/.env"; set +a; exec /absolute/path/to/repo/.venv/bin/upscayl-mcp'
```
Enable only `upscale_image`, `list_models`, and `worker_status`. Then verify both
discovery and a real GPU job:
```bash
hermes mcp test upscayl
python3 scripts/create_fixture.py io/upscayl-mcp-fixture.png
uv run python scripts/verify-mcp.py io/upscayl-mcp-fixture.png
```
Run `/reload-mcp` or start a new Hermes session after changing the MCP setup.
Image bytes travel directly between the local bridge and authenticated worker;
they never enter model context.
## Maintainer/local image build
Consumers do not need Upscayl or Flatpak installed on the Docker host. These
steps are only for maintainers who want to build the image locally from their
currently installed Flatpak:
```bash
scripts/stage-from-flatpak.sh
scripts/build-local.sh
UPSCAYL_IMAGE=local/upscayl-api:latest \
UPSCAYL_PULL_POLICY=never \
scripts/deploy-api.sh
```
`stage-from-flatpak.sh` copies the backend, four reviewed model pairs, and
Flatpak provenance from the reviewed `org.upscayl.Upscayl` Flatpak
into ignored `vendor/` paths. It rejects unexpected Flatpak versions, OSTree
commits, manifests, binaries, or model hashes. `scripts/build-local.sh` refuses
to build when any required asset or provenance file is missing. Matching the
build process does not prove byte-for-byte reproduction of a published artifact;
use the recorded Flatpak commit, image digest, and asset hashes for exact provenance.
UltraSharp and UltraMix Balanced retain CC BY-NC-SA 4.0/non-commercial terms;
see `THIRD_PARTY_NOTICES.md` before redistributing or using the complete image
commercially.
## NVIDIA/Vulkan compatibility
NVIDIA Container Toolkit 1.19.x on Debian 13 can expose CUDA while omitting
libraries needed by Vulkan. The deployment mounts the exact host-driver versions
of these libraries read-only:
- `libnvidia-eglcore`
- `libnvidia-glsi`
- `libnvidia-tls`
- `libnvidia-glcore`
- `libnvidia-gpucomp`
- `libnvidia-glvkspirv`
The image also installs `libegl1`. Re-run both GPU and API verification after an
NVIDIA driver or container-toolkit update.
## Worker API
- `GET /healthz` — unauthenticated process liveness
- `GET /readyz` — authenticated model/GPU readiness and queue depth
- `GET /v1/models` — authenticated model capabilities
- `POST /v1/jobs` — authenticated multipart submission
- `GET /v1/jobs/{id}` — authenticated status
- `GET /v1/jobs/{id}/result` — authenticated PNG result
- `DELETE /v1/jobs/{id}` — authenticated terminal-job cleanup
`POST /v1/jobs` accepts scales from 1 through 16, `image_type` as `auto`,
`photo`, or `digital`, and `model` as `auto` or an allow-listed ID.
## Development
```bash
uv sync --frozen --extra test
uv run pytest -q
uv run ruff check upscayl_worker upscayl_bridge tests scripts
uv run ruff format --check upscayl_worker upscayl_bridge tests scripts
```
See [`docs/mcp-architecture.md`](docs/mcp-architecture.md) for the implemented
integration boundary and [`docs/operations.md`](docs/operations.md) for
deployment limits, token rotation, cleanup, and release verification.
## Licensing
Original repository code and deployment glue are MIT licensed. The published
container also contains unmodified third-party Upscayl runtime and model assets
with separate licenses and attribution requirements, including non-commercial
terms for some models. See [`NOTICE`](NOTICE) and
[`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) before redistribution or
commercial use.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues