Sign inSign up

longnguyen58445/offload-kit:0.2.4

Manifest digest

sha256:2bee7c7ee477a349645f6cf9ae9f29636f30c0ce79ba604ab6112f061ea76b3f

Last pushed

12 days by longnguyen58445

Type

Sandbox Kit

Manifest digest

sha256:2bee7c7ee477a349645f6cf9ae9f29636f30c0ce79ba604ab6112f061ea76b3f

yaml
schemaVersion: "2"
kind: mixin
name: offload
displayName: Docker Offload
description: Installs the Docker Offload CLI plugin (v0.6.85, linux amd64 and arm64, from a plugin image) set to the Cloud Sandboxes engine (Offload v2), so agents can run Docker workloads on a remote engine through the docker-cloud context. Signs in with a Docker personal access token bound on the host.
args:
    pluginImage:
        default: longnguyen58445/offload-plugin:0.6.85
        description: Image holding the docker-offload plugin at /docker-offload (built from kits/offload-plugin). Its binary must match the checksums pinned in the startup script.
        pattern: ^[a-z0-9][a-z0-9._/:@-]*$
    stage:
        default: ""
        description: Set to 1 to use Docker's staging environment (needs a staging PAT). Empty is production.
        pattern: ^1?$
    username:
        default: ""
        description: Docker ID the personal access token belongs to. Leave empty to sign in by hand with docker login.
        pattern: ^[a-z0-9._-]*$
agentInstructions:
    content: |
        ## Docker Offload

        The `docker offload` CLI plugin is installed and set to the Cloud
        Sandboxes engine. It runs Docker workloads on a remote engine (a Cloud
        Sandbox) instead of this sandbox's own Docker daemon. Use it for heavy
        builds, large images, or GPU work; it costs money while the remote engine
        runs, so stop it when you are done.

            docker offload status                      # signed in? engine running?
            docker offload create                      # start a remote engine (minutes on first create)
            docker offload create --cpus 8 --memory 16g
            docker build .                             # now runs on the remote engine
            docker run --rm alpine uname -a
            docker offload stop                        # park it (keeps containers, stops billing for compute)
            docker offload rm                          # delete it and everything on it

        `docker offload create` makes `docker-cloud` the current docker context,
        so every docker command after it runs on the remote engine. Switch back
        to this sandbox's own daemon with `docker context use default`, or target
        one engine per command with `--context default` / `--context docker-cloud`.

        Sign-in: the daemon uses the Docker login in ~/.docker/config.json, read
        when the daemon starts. If `docker offload status` shows no account, ask
        the user to run `docker login` (or bind a PAT with
        `sbx secret set docker-offload`); after a login, run
        `docker offload daemon stop` so the next command starts a daemon that
        sees it.
        Startup output is in /tmp/offload-start.log and the daemon log in
        ~/.docker/cloud/logs/cloud-daemon.log.
permissions:
    network:
        allow:
            - hub.docker.com:443
            - login.docker.com:443
            - index.docker.io:443
            - auth.docker.io:443
            - registry-1.docker.io:443
            - production.cloudfront.docker.com:443
            - production.cloudflare.docker.com:443
            - api.sandboxes-cloud.docker.com:443
            - ssh.sandboxes-cloud.docker.com:22
            - 100.22.208.130:22
            - 52.39.3.133:22
            - 54.212.241.121:22
            - api.offload.docker.com:443
            - hub-stage.docker.com:443
            - login-stage.docker.com:443
            - index-stage.docker.io:443
            - api.sandboxes-cloud-stage.docker.com:443
credentials:
    - service: docker-offload
      description: Docker personal access token for the account that has Cloud Sandboxes access. The startup script signs in with it, so it is present in the sandbox (and in ~/.docker/config.json); scope it narrowly.
      apiKey:
        name: DOCKER_OFFLOAD_PAT
environment:
    variables:
        DOCKER_OFFLOAD_STAGE: ${{ kit.args.stage }}
        DOCKER_OFFLOAD_USER: ${{ kit.args.username }}
        OFFLOAD_PLUGIN_IMAGE: ${{ kit.args.pluginImage }}
setup:
    startup:
        - command:
            - sh
            - -c
            - nohup /home/agent/.local/bin/offload-start.sh > /tmp/offload-start.log 2>&1 &
          user: "1000"
          description: Install the plugin from its image, sign in to Docker when a PAT is bound, then start the Offload daemon in the background
    files:
        - path: /home/agent/.docker/cloud/config-override.toml
          content: |
            # Selects the Cloud Sandboxes engine (Offload v2). Without Docker
            # Desktop there is no feature flag to read, and the daemon would
            # otherwise fall back to the leased engine (Offload v1).
            [features]
            cloud_sandbox_engine = true
          mode: "0644"
          description: Select the Cloud Sandboxes engine
        - path: /home/agent/.docker/cloud-stage/config-override.toml
          content: |
            # The same, for staging (stage=1), whose daemon keeps its state here.
            [features]
            cloud_sandbox_engine = true
          mode: "0644"
          description: Select the Cloud Sandboxes engine in staging
        - path: /home/agent/.local/bin/offload-start.sh
          content: |
            #!/bin/sh
            # Installs the plugin from its image, signs in with the bound PAT, if
            # any, then starts the daemon. Any docker offload command would start
            # the daemon too; starting it here just makes the first one faster.
            set -u
            image="${OFFLOAD_PLUGIN_IMAGE}"
            plugin=/home/agent/.docker/cli-plugins/docker-offload
            case "$(uname -m)" in
              x86_64|amd64) sha=0f4f346fb1faad059e841451e274b0a7fcf8a741038f3e5563cce5030b19c235 ;;
              aarch64|arm64) sha=c973a45b639c61b22c6568a644a8cdbcf74409e2c574b0f8907fcdc87a84eff6 ;;
              *) echo "offload: unsupported arch $(uname -m) (expected amd64 or arm64)" >&2; exit 1 ;;
            esac
            # Always this sandbox's own daemon: after a create, the current
            # context is the remote engine.
            d() { docker --context default "$@"; }
            # The sandbox's docker may still be starting.
            i=0
            until d info >/dev/null 2>&1; do
              i=$((i + 1))
              if [ "$i" -ge 60 ]; then echo "offload: docker did not come up" >&2; exit 1; fi
              sleep 2
            done
            if ! echo "$sha  $plugin" | sha256sum -c - >/dev/null 2>&1; then
              # Retried: at sandbox start the pull can be refused (403) for a
              # while before it goes through. A copy already here will do if the
              # registry cannot be reached at all.
              i=0
              until d pull -q "$image"; do
                i=$((i + 1))
                if [ "$i" -ge 12 ]; then
                  d image inspect "$image" >/dev/null 2>&1 ||
                    { echo "offload: could not pull $image" >&2; exit 1; }
                  break
                fi
                sleep 10
              done
              # A scratch image has no command; one is given so it can be created.
              ctr=$(d create "$image" /docker-offload) || exit 1
              mkdir -p "$(dirname "$plugin")"
              d cp "$ctr:/docker-offload" "$plugin.new"
              d rm "$ctr" >/dev/null
              # The image tag can move; the binary must be the pinned one.
              if ! echo "$sha  $plugin.new" | sha256sum -c -; then
                echo "offload: $image does not hold the pinned plugin" >&2
                rm -f "$plugin.new"
                exit 1
              fi
              chmod 0755 "$plugin.new" && mv "$plugin.new" "$plugin"
            fi
            docker offload version

            # Staging reads its login from the stage registry's entry.
            registry=
            [ -n "${DOCKER_OFFLOAD_STAGE:-}" ] && registry=index-stage.docker.io
            if [ -n "${DOCKER_OFFLOAD_PAT:-}" ] && [ -n "${DOCKER_OFFLOAD_USER:-}" ]; then
              if printf '%s' "$DOCKER_OFFLOAD_PAT" | docker login -u "$DOCKER_OFFLOAD_USER" --password-stdin $registry; then
                echo "offload: signed in to Docker as $DOCKER_OFFLOAD_USER"
              else
                echo "offload: docker login failed; sign in by hand with docker login" >&2
              fi
            else
              echo "offload: no PAT or username bound; sign in by hand with docker login"
            fi
            # The daemon reads ~/.docker/config.json once, when it starts, so one
            # started before a login never sees it. Start it only when a login is
            # already there; otherwise the first docker offload command after
            # docker login starts it.
            if grep -q "${registry:-index.docker.io}" /home/agent/.docker/config.json 2>/dev/null; then
              exec docker offload daemon start
            fi
            echo "offload: not signed in yet; the daemon starts on the first docker offload command after docker login"
          mode: "0755"
          description: Startup script that signs in and starts the Offload daemon
        - path: /home/agent/.claude/skills/docker-offload/SKILL.md
          content: |
            ---
            name: docker-offload
            description: Run Docker builds and containers on a remote Docker Offload engine (a Cloud Sandbox) instead of this sandbox's own Docker daemon. Use when a build or workload is too heavy for the sandbox, needs more CPU or memory, or when the user asks to use Docker Offload. Covers checking sign-in, creating the engine, switching contexts, verifying it works, and cleaning up so it does not keep billing.
            ---

            # Docker Offload

            The remote engine bills while it runs. Create it only when the work
            needs it, and stop or remove it when the work is done.

            ## 1. Check before creating

            ```sh
            docker offload status
            ```

            - `Account:` shows a Docker ID: signed in. Continue.
            - `Account: N/A`: not signed in. Do not try to sign in with a password
              or token you were not given. Tell the user to bind a PAT
              (`sbx secret set docker-offload` on the host, then restart the
              sandbox) or to run `docker login` themselves. Stop here.
            - `docker: unknown command: docker offload`: the plugin is missing;
              the kit's install did not run. Tell the user.
            - The status shows a running sandbox engine already: reuse it, skip
              to step 3.

            ## 2. Create

            ```sh
            docker offload create                       # default size
            docker offload create --cpus 8 --memory 16g # bigger, if the work needs it
            ```

            The first create takes a few minutes. It ends with `✓ Sandbox ready.`
            and a `Size:` line. If the user asked for a size and the sandbox
            already exists at another size, `create` asks before recreating it.
            Recreating deletes everything on it, so do not pass `--recreate`
            unless the user agreed to lose its containers.

            ## 3. Use it

            `create` makes `docker-cloud` the current context, so plain docker
            commands now run remotely. Confirm before running real work:

            ```sh
            docker context show                 # docker-cloud
            docker run --rm alpine uname -a     # runs on the remote engine
            ```

            The engine pauses after 5 idle minutes (`--idle-timeout` sets it,
            `--keep-alive` turns pausing off). A docker command that needs it,
            such as `run`, `start` or `attach`, resumes it; that first command
            can take a few minutes, which is not a hang. Attached sessions are
            cut off by a pause; reattach with `docker attach`.

            To use this sandbox's own daemon for a command, pass
            `--context default`. Published ports (`-p 8080:80`) are forwarded to
            this sandbox, so `curl localhost:8080` reaches the remote container.

            ## 4. Clean up

            When the work is done, unless the user wants the engine kept:

            ```sh
            docker offload stop          # parks it; containers and images are kept
            docker context use default   # back to this sandbox's daemon
            ```

            Use `docker offload rm` only when the user wants the engine and
            everything on it deleted.

            ## Troubleshooting

            Startup output is in `/tmp/offload-start.log`; the daemon log is
            `~/.docker/cloud/logs/cloud-daemon.log` (`cloud-stage` in staging).

            | Symptom | Meaning | What to do |
            |---|---|---|
            | `create` and `rm` missing from `docker offload --help` | the Offload v2 override file is missing | check `~/.docker/cloud/config-override.toml` has `cloud_sandbox_engine = true` |
            | `sign in to continue using Docker Offload` right after a successful `docker login` | the daemon was already running and only reads the login when it starts | `docker offload daemon stop`, then run the command again (it starts a fresh daemon) |
            | `Account: N/A` after a bound PAT | `docker login` failed at startup | read `/tmp/offload-start.log`; the PAT may be expired or for another account |
            | `create` says the account has no access | the account is not entitled to Cloud Sandboxes | tell the user; nothing to fix in the sandbox |
            | HTTP 403 naming a host | the sandbox network policy blocks it | tell the user which host; they allow it on the host with `sbx policy allow network <host>` |
            | `ssh: handshake failed: EOF`, repeating every 10s in the daemon log | the policy blocks SSH to the gateway; SSH has no host name, so only an `IP:22` rule matches | ask the user for `sbx policy log` on the host and an allow for each blocked `IP:22`; stop the engine (`docker offload stop`) while it cannot connect |
            | `still pausing from the last stop` | the engine is still parking | wait a minute and run `create` again |
            | a warning that Desktop could not ensure the daemon | expected: there is no Docker Desktop here | ignore it |
          mode: "0644"
          description: Claude Code skill for using Docker Offload safely from the sandbox