Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

AKernel Python SDK

akernel-sdk is the Python interface for creating and managing remote AKernel sandboxes. Applications use one stable API for commands, files, interactive PTYs, port forwarding, and reverse tunnels.

Navigation

Install and configure

AKernel SDK requires Python 3.10 or newer.

Install the stable SDK from PyPI:

python -m pip install akernel-sdk

To include published release candidates:

python -m pip install --pre akernel-sdk

Choose an SDK version compatible with the deployed backend and pin its exact published version for reproducible installations. For an unpublished source revision, install from the repository root:

python -m pip install ./sdk/python

The SDK's ADX dependency is declared in pyproject.toml. The ADX adapter translates the public AKernel Sandbox API to adx_environment.Environment. Deploy a matching ADX release exposing /api/environment/v1 for lifecycle, resources and command subscriptions.

Source installs and CI resolve the declared ADX dependency from the package index. CI checks AKernel wheel and source distribution installs in separate clean environments, including pip check, imports, version checks, and the ak CLI.

Configure the public AKernel entrypoint and the deployment token:

export AKERNEL_SERVER_ADDRESS="akernel.example.com"
export AKERNEL_TOKEN="<token>"

Address behavior is deterministic:

  • A host or IP without a port uses HTTPS/WSS on 443 for the frontend and HTTP on 80 for public sandbox port URLs.
  • host:port preserves shared-port behavior: control uses HTTPS/WSS and public sandbox URLs use HTTP/WS on that explicit port.
  • AKERNEL_GATEWAY_ADDRESS overrides only the port-forwarding and reverse tunnel data endpoint for nonstandard ports or TLS gateways. An override without a scheme uses HTTP/WS. Exec and file transfer continue to use AKERNEL_SERVER_ADDRESS.

TLS certificate verification

AKERNEL_VERIFY_TLS controls certificate-chain and hostname verification for the SDK's HTTPS control, command and file requests, command-watch WebSockets, PTYs, CLI inventory/deletion and resource queries. It defaults to 0 to retain the current self-signed deployment behavior. HTTPS still encrypts the connection with this default, but does not authenticate the server certificate.

For an entrypoint with a certificate trusted by the default certificate stores:

export AKERNEL_VERIFY_TLS=1

For a private CA or a standalone self-signed certificate, additionally configure the trusted PEM bundle before the first SDK operation:

export AKERNEL_VERIFY_TLS=1
export SSL_CERT_FILE="/path/to/trusted-ca-or-certificate.pem"

The standalone operator can provide the existing <data-directory>/adx/tls/ingress-public.pem file. For Kubernetes, the operator can provide public.pem from the configured ADX TLS Secret. This is public certificate material; clients do not need the server's private key or the administrator API key to establish certificate trust. Use the hostname or IP listed in the certificate's subject alternative names. The default standalone certificate covers localhost and 127.0.0.1; replace it with a certificate matching the public hostname for remote access.

The CLI accepts global options before the subcommand:

ak --verify-tls list
ak --ca-file /path/to/trusted-ca-or-certificate.pem resources
ak --ca-file /path/to/trusted-ca-or-certificate.pem exec <sandbox-id>

--ca-file configures SSL_CERT_FILE and enables verification. The explicit --no-verify-tls option selects the existing unverified mode and cannot be combined with --ca-file. Environment boolean values accept 1/true/yes/on and 0/false/no/off; invalid values fail rather than disabling verification.

CA loading follows the underlying transport when verification is enabled:

Transport Trust-source contract
SDK control, command and file HTTP requests (HTTPX) A nonempty SSL_CERT_FILE takes precedence over SSL_CERT_DIR and is loaded explicitly. A missing or malformed file raises an error during client construction, even if the directory contains a usable CA. If no file is selected, HTTPX uses SSL_CERT_DIR when set, otherwise its default CA bundle.
CLI inventory/deletion, SDK resource queries, PTY and command-watch (Python SSL/OpenSSL) Use OpenSSL's default trust paths, including SSL_CERT_FILE and SSL_CERT_DIR. A missing or malformed file can be ignored while a usable CA directory still validates the server.

--ca-file sets the process trust-file variable; it does not restrict all transports to that file as their sole trust source or guarantee an error for an invalid path. Check the selected PEM file before use. For a private CA, configure a valid file and unset SSL_CERT_DIR to avoid an additional environment-selected directory; OpenSSL can still use its default trust paths. These loading differences do not disable certificate-chain or hostname checks in verified mode. When verification is off, these service transports do not load the CA sources to authenticate the server.

Verification failures do not fall back to unverified TLS. The process backend and dependency HTTP pools retain their configuration, so set trust and verification before first use; restart the client after changing CA material.

Certificate issuance and renewal remain deployment responsibilities. Registry TLS, reverse-tunnel upstream HTTPS trust and SSH host keys have separate configuration; this setting controls the AKernel service connection.

Create a sandbox

from akernel_sdk import Sandbox

with Sandbox(cpu=1000, memory=2048) as sandbox:
    result = sandbox.commands.run("printf hello")
    print(result.stdout)

The constructor accepts:

Sandbox(
    image: str | None = None,
    rootfs: S3Config | None = None,
    runtime: str = "runsc",
    cpu: int = 1000,
    memory: int = 4096,
    cpu_limit: int = 0,
    mem_limit: int = 0,
    idle_timeout: int = 300,
    schedule_timeout: int = 30,
    env: dict[str, str] | None = None,
    name: str | None = None,
    cwd: str | None = None,
    port_forwardings: list[int] | None = None,
    mounts: list[Mount] | None = None,
    reverse_tunnel: HttpReverseTunnel | None = None,
    detached: bool = False,
    node_id: str | None = None,
    *,
    xpu: str | None = None,
    storage_mb: int | None = None,
    storage_limit_mb: int = 0,
    network_policy: NetworkPolicy | None = None,
    dockerfile: DockerfileLaunch | None = None,
    extra_config: Mapping[str, object] | None = None,
)

Experimental GPU and writable storage

Request a whole NVIDIA GPU by type, exact product model, and count:

with Sandbox(xpu="gpu:l20:1") as sandbox:
    print(sandbox.commands.run("nvidia-smi -L").stdout)

The type:model:count value is case-insensitive and canonicalized to lower case. The model is required and matched exactly; wildcard models are not supported. The bundled backend currently requires the gVisor runsc runtime and a node configured for gVisor nvproxy. Runtime compatibility is validated by the backend rather than the SDK.

Set the storage scheduling quota and actual writable root filesystem hard limit in MiB:

with Sandbox(storage_mb=4 * 1024, storage_limit_mb=20 * 1024) as sandbox:
    print(sandbox.commands.run("df -h /").stdout)

storage_mb is the scheduling quota: the storage amount reserved when placing the sandbox. storage_limit_mb is the actual hard limit on writable-root capacity. In this example, scheduling reserves 4 GiB while the writable root can grow up to 20 GiB. Reserving less than the hard limit permits storage overcommit; the limit does not guarantee that the node has enough physical space for all sandboxes to reach their limits simultaneously.

storage_limit_mb=0 follows storage_mb, or the cluster default when the scheduling quota is omitted. If only a positive limit is given, the backend also reserves that amount for scheduling. A positive limit must be at least the explicit scheduling quota. Limits must be nonnegative integers; booleans are rejected.

Explicit writable storage quotas are supported by runsc and Firecracker and use sandboxd's disk-backed filestore (ext4 in the bundled standalone deployment, XFS in the Terraform-managed Aliyun profile). Runtime compatibility is validated by the backend. If storage_limit_mb is not supported by the selected backend, a nonzero value fails explicitly. See examples/gpu_sandbox.py and examples/storage_sandbox.py.

Network ACLs

Omit network_policy to leave all sandbox networking unrestricted. An empty NetworkPolicy() is equivalent and is omitted from the creation request:

from akernel_sdk import NetworkPolicy, Sandbox

with Sandbox() as unrestricted:
    print(unrestricted.commands.run("python3 -c 'import socket; "
                                    "socket.getaddrinfo(\"github.com\", 443)'"))

Block new sandbox flows except the YuanRong control proxy and published sandbox-port routes with the compatibility helper:

with Sandbox(network_policy=NetworkPolicy.block()) as sandbox:
    result = sandbox.commands.run("printf 'control plane still works'")
    assert result.exit_code == 0

Commands and lifecycle operations continue to work in block mode. The policy also publishes the sandbox targets required by direct filesystem I/O, reverse tunnels, and explicit port_forwardings. Replies on those allowed paths are stateful. Other new network flows remain denied.

Deny conventional DNS lookups for exact names or leading *. suffix patterns:

policy = NetworkPolicy.deny_dns("github.com", "*.github.com")
with Sandbox(network_policy=policy) as sandbox:
    blocked = sandbox.commands.run(
        "python3 -c 'import socket; socket.getaddrinfo(\"github.com\", 443)'"
    )
    assert blocked.exit_code != 0

An exact pattern matches only that name. For example, github.com does not match api.github.com, while *.github.com matches descendants but not the apex. Supply both when both should be denied. Patterns are normalized to lower-case ASCII without a trailing dot; international names are converted to punycode. Each pattern may be an exact name or begin with one leading *.. The remaining name is at most 253 characters; each dot-separated label is 1-63 letters, digits, underscores, or hyphens, and a hyphen cannot start or end a label. Other wildcard placements and ? are rejected.

Create an egress allowlist with the high-level helper. Rules may select an IPv4 address or CIDR, an exact or leading-wildcard DNS name, TCP or UDP peer ports, and a priority:

from akernel_sdk import NetworkRule, PortRange

policy = NetworkPolicy.allowlist(
    [
        NetworkRule(
            domain="*.example.com",
            protocol="tcp",
            port_range=PortRange(443),
            priority=200,
        ),
        NetworkRule(
            cidr="192.0.2.10",
            protocol="tcp",
            port_range=PortRange(8000, 8010),
        ),
    ]
)
with Sandbox(network_policy=policy) as sandbox:
    print(sandbox.commands.run("curl https://api.example.com").stdout)

Replace the complete policy of a running sandbox atomically with update_network_policy. Passing None or an empty NetworkPolicy() clears the policy and restores unrestricted networking:

with Sandbox() as sandbox:
    sandbox.update_network_policy(NetworkPolicy.block())
    sandbox.update_network_policy(
        NetworkPolicy.deny_dns("github.com", "*.github.com")
    )
    sandbox.update_network_policy(None)

The desired policy survives sandboxd restarts, explicit reloads, and same-node failover. Dynamic replacement is supported by the default bundled backend.

For independent ingress and egress defaults, deny rules, sandbox-side port ranges, DNS allowlists, or stateless matching, construct the schema v2 model directly with TrafficPolicy, NetworkRule, DNSPolicy, and DNSRule. Traffic rules are evaluated by highest priority first; an equal-priority deny wins. Stateful mode is the default and permits reply traffic. Priority 4294967295 is reserved for control-plane and published-port rules, so user priorities are limited to 1..4294967294. In stateless mode, a protected published-port rule covers ingress only; add an explicit egress rule for the matching sandbox source port when the application must send a reply. This avoids turning a published port into an unrestricted egress escape hatch.

Domain traffic rules authorize IPv4 addresses learned from an allowed original DNS query, following its complete CNAME chain. The authorization uses the answer TTL, clamped to 1..3600 seconds, and is replaced when the name is resolved again with an IPv4 A or ANY query. Existing connections that depended on an expired or replaced authorization are removed. Parallel AAAA queries do not revoke IPv4 grants. DNS names not covered by a domain traffic rule can still resolve when the DNS policy allows the query, but their answers do not grant packet access. While a DNS policy or domain traffic rule is active, ordinary TCP and UDP DNS is accepted only through sandboxd's managed resolver; a traffic rule for another port-53 resolver does not bypass it. The resulting enforcement is at IPv4 and transport layers. Another virtual host sharing an authorized address and port is not distinguishable; use an application proxy when hostname-level isolation is required.

Network policy replacement uses whole-policy semantics. The legacy block_network and dns_blacklist fields cannot be combined with schema v2 sections. DNS policies cover ordinary UDP and TCP DNS and return a refused response for denied queries; DNS-over-HTTPS and connections to a known IP are outside DNS filtering. Packet rules are IPv4. sandboxd accepts 256 combined traffic rules; the backend reserves entries from that limit for the Function Proxy and each distinct published sandbox port. IPv6 traffic is dropped whenever a traffic or DNS policy is active, preventing an alternate resolver from bypassing the IPv4 policy. Arbitrary non-IP Ethernet protocols are outside the portable ACL contract. Domain and DNS patterns are normalized through IDNA.

See examples/network_policy.py for the compatibility modes and generic allowlist. Deployment nodes must have network ACL support enabled; the bundled standalone, Helm, and Terraform configurations enable it. Drain existing sandboxes before upgrading a node to an ACL-enabled sandboxd configuration, as described in the deployment guide.

Sandbox runtimes

AKernel uses the gVisor runsc runtime when runtime is omitted. Runtime identifiers and optional extra_config are forwarded to the selected backend instead of being restricted or interpreted by an SDK-owned registry. Values in extra_config must be JSON-compatible. The bundled deployment advertises runsc, Kata Containers, and Firecracker by default when their host prerequisites are available.

Kata and Firecracker require at least one cluster node whose sandboxd instance successfully initialized the requested runtime with a usable /dev/kvm device. Nodes without KVM remain available for runsc workloads and do not advertise either VM runtime. The bundled Firecracker configuration enables read-only virtio-fs for OCI/Nydus image roots and read-only host directories, while retaining EROFS image roots and mounts. OCI image mounts, writable host binds, GPUs, and nested KVM remain unsupported.

The all-in-one image can optionally package a native Linux runc backend. Operators build it with AKERNEL_ENABLE_RUNC=true and enable it explicitly because it provides host-kernel container isolation, not the user-space kernel boundary of runsc. Guided cloud profiles use make config ENABLE_RUNC=true; standalone deployments use AKERNEL_ENABLE_RUNC=true, and direct Helm deployments use node.config.sandboxd.enableRunc=true. After it is advertised:

with Sandbox(runtime="runc") as sandbox:
    print(sandbox.commands.run("uname -s").stdout)

with Sandbox(
    runtime="runc",
    extra_config={"enableKVM": True},
) as sandbox_with_kvm:
    print(sandbox_with_kvm.commands.run("test -c /dev/kvm").exit_code)

enableKVM is owned by the runc backend and requires a usable /dev/kvm on the selected node. Runc supports OCI/EROFS root filesystems, read-only mounts, networking, command execution, and the default writable overlay. Experimental GPU requests remain runsc-only; explicit storage_mb quotas are supported by runsc and Firecracker. See the sandbox runtime comparison for the runtime capability boundaries.

See examples/sandbox_runtime.py for a runnable example.

Commands

Run a foreground command:

result = sandbox.commands.run(
    "printf $GREETING",
    envs={"GREETING": "hello"},
    cwd="/tmp",
    timeout=60,
)
print(result.stdout, result.stderr, result.exit_code)

Run and control a background command:

handle = sandbox.commands.run("sleep 30", background=True)
print(handle.pid)

for process in sandbox.commands.list():
    print(process.pid, process.command, process.running)

handle.kill()

Enable stdin only when it is needed:

handle = sandbox.commands.run("wc -l", background=True, stdin=True)
handle.send_stdin("one\ntwo\n")
handle.close_stdin()
result = handle.wait(timeout=15)

Foreground commands return a backend-neutral CommandResult. Background commands return an AKernel CommandHandle; its lifecycle operations are delegated to the selected backend.

With the ADX backend, handle.wait(timeout) returns CommandResult(exit_code=None, status="RUNNING", error_code="WAIT_TIMEOUT") when the wait deadline expires. The command keeps running; a later wait() can observe its completion. handle.kill() and sandbox.commands.kill(pid) return False when the command does not exist or has already finished.

PTY session wrappers are not retained by the factory after the caller releases them. A successful wait() also removes the completed session from its tracked sessions. A wait timeout keeps the session active; use close() or a context manager to terminate an interactive session explicitly.

Filesystem

sandbox.files.write("/tmp/message.txt", "hello")
print(sandbox.files.read("/tmp/message.txt"))

sandbox.files.write("/tmp/data.bin", b"\x00\x01")
print(sandbox.files.read("/tmp/data.bin", format="bytes"))

for entry in sandbox.files.list("/tmp"):
    print(entry.path, entry.type, entry.size)

sandbox.files.make_dir("/workspace")
sandbox.files.rename("/tmp/message.txt", "/workspace/message.txt")
sandbox.files.remove("/workspace/message.txt")

Copy local files or directories through the frontend exec WebSocket:

sandbox.files.copy_from_local("./project", "/workspace/project")
sandbox.files.copy_to_local("/workspace/result.json", "./result.json")

Interactive PTYs

Use sandbox.pty for an interactive byte stream with stdin, streaming output, terminal resizing, and an exit status:

import sys

from akernel_sdk import Sandbox


def write_output(data: bytes) -> None:
    sys.stdout.buffer.write(data)
    sys.stdout.buffer.flush()


with Sandbox() as sandbox:
    with sandbox.pty.create(on_data=write_output) as session:
        session.send_stdin(b"echo hello from PTY\n")
        session.resize(rows=40, cols=120)
        session.send_stdin(b"exit 7\n")
        print(session.wait())

PTY output remains bytes so the SDK does not guess the terminal encoding. Use session.close_stdin() to signal end-of-input while continuing to receive output. A session belongs to its WebSocket connection: closing it terminates the remote interactive process, and reconnecting to an existing session is not supported.

Use sandbox.commands instead when the caller needs separate stdout and stderr, a complete CommandResult, or a controllable background process. The former Shell API and its actor bash_* methods were removed before the v0.1.0 public API was released.

Port forwarding

Declare each sandbox port at creation time:

from akernel_sdk import Sandbox

with Sandbox(port_forwardings=[8080]) as sandbox:
    server = sandbox.commands.run(
        "python3 -m http.server 8080 --bind 0.0.0.0",
        background=True,
    )
    print(sandbox.get_port_url(8080))
    server.kill()

get_port_url() rejects undeclared ports. Pass internal=True only when a deployment operator explicitly wants the direct ADX Ingress address instead of the public gateway.

Local failover and reload

Sandbox(failover=True) opts into same-node recovery of the same logical sandbox after its physical runtime fails. sandbox.reload() requests the same rollback explicitly. It returns False whenever the rollback is not completed, including when no usable local anonymous checkpoint exists, the sandbox is already closed, or the backend reports an operational failure. A successful reload preserves sandbox.id and the existing commands, filesystem, and PTY facades. The SDK also confirms the data route with a read-only process listing before reporting success. A temporary route conflict is retried within a 10-second window; it never reissues the rollback. Other errors, explicit non-retryable errors, or an expired wait return False.

Recovery points are local and follow the source sandbox lifecycle. They are created by sandbox workloads through Execd's internal POST /checkpoint endpoint on /run/akernel/execd.sock. A successful request returns {"status":"completed"}; a concurrent checkpoint request returns HTTP 409. This Unix-socket protocol is experimental and is not a stable public AKernel SDK interface. The SDK deliberately does not expose checkpoint identifiers, restore, list, or delete operations.

The bundled runsc and Firecracker runtimes support this recovery flow. The maintained example validates it with runsc and installs curl in the sandbox before calling the internal Unix-socket endpoint, so the default RRT runtime profile is sufficient:

AKERNEL_TEST_RUNTIME=runsc python examples/failover_reload.py

See examples/failover_reload.py for the internal trigger used during integration.

Reverse tunnels

A reverse tunnel lets sandbox code call an HTTP or HTTPS service reachable from the machine running the SDK:

from akernel_sdk import HttpReverseTunnel, Sandbox

tunnel = HttpReverseTunnel(
    target="https://service.example.com",
    reverse_port=8765,
    listen_port=8766,
    connect_timeout=60,
)

with Sandbox(reverse_tunnel=tunnel) as sandbox:
    result = sandbox.commands.run(
        f"curl {sandbox.reverse_tunnel.url}/health"
    )

reverse_port carries the WebSocket tunnel through ADX Ingress. listen_port is the loopback HTTP listener used inside the sandbox. Consequently, sandbox.reverse_tunnel.url is always http://127.0.0.1:<listen_port>, even when target uses HTTPS.

For an HTTPS target, the SDK-side tunnel client performs the TLS handshake and certificate verification. The sandbox application talks only to its loopback HTTP listener. AKernel supports one HTTP/HTTPS reverse tunnel per sandbox and does not expose a general TCP tunnel.

The bundled backend supports custom internal tunnel ports. Its frontend derives the WebSocket port from the HTTP listener, so reverse_port must equal listen_port - 1. Both ports are reserved inside that sandbox while the tunnel is active and must not also appear in port_forwardings; they do not occupy ports on the SDK host.

Rootfs and mounts

Use a public OCI image:

with Sandbox(image="ubuntu:24.04") as sandbox:
    print(sandbox.commands.run("cat /etc/os-release").stdout)

To start the effective OCI image ENTRYPOINT and CMD as the managed sandbox workload, enable entrypoint inheritance and wait for its exit status:

with Sandbox(image="example/worker:latest", inherit_entrypoint=True) as sandbox:
    exit_code = sandbox.wait_entrypoint()
    print(exit_code, sandbox.entrypoint_exit_info)

For this image-inheritance path, sandbox.startup_command is None. wait_entrypoint() returns an integer exit code; entrypoint_exit_info contains the structured exit details collected by that call. Waiting observes process exit, not application readiness. An inherited process exiting after successful sandbox creation does not by itself terminate the sandbox. For a long-running service, check its application health endpoint separately.

Sandbox(cwd=...) sets the default working directory for subsequent sandbox.commands.run() calls that omit cwd. With inherit_entrypoint=True, the image process starts in the image's OCI WORKDIR; the constructor's cwd does not override it. For example, if the image declares WORKDIR /app:

with Sandbox(
    image="example/worker:latest", inherit_entrypoint=True, cwd="/tmp"
) as sandbox:
    # The inherited image entrypoint starts in /app.
    print(sandbox.commands.run("pwd").stdout)  # /tmp
    print(sandbox.commands.run("pwd", cwd="/").stdout)  # /

Or use an object in S3-compatible storage as the rootfs:

from akernel_sdk import S3Config, Sandbox

rootfs = S3Config(
    endpoint="https://s3.example.com",
    bucket="akernel-rootfs",
    object="ubuntu-24.04/rootfs.img",
    access_key="<optional>",
    secret_key="<optional>",
)

with Sandbox(rootfs=rootfs) as sandbox:
    print(sandbox.commands.run("cat /etc/os-release").stdout)

image and rootfs are mutually exclusive. The SDK generates the backend wire representation; callers do not pass raw rootfs JSON or override the runtime inside an S3 object. When neither source is supplied, AKernel sends only the selected isolation runtime and openYuanRong overlays it onto the rootfs configured by the deployed service.

Firecracker supports Sandbox(runtime="firecracker", image="ubuntu:24.04") with the bundled virtio-fs configuration. Both OCI and Nydus roots use the image manager's directory directly, without conversion to EROFS. The shared root stays read-only; sandbox writes use its private ext4 overlay. The deployed default and explicit rootfs S3 objects continue to use raw EROFS images. The bundled distill-fs supports Nydus RAFS v5. When preparing Nydus images with nydusify convert, select --fs-version 5 explicitly. Custom deployments must enable plugin.runtime.firecracker.virtiofs_enabled and install the matching Firecracker stack and virtiofsd.

The same S3Config type can be used as a read-only mount source:

from akernel_sdk import Mount

mount = Mount(target="/models", type="erofs", s3_config=rootfs)
with Sandbox(mounts=[mount]) as sandbox:
    print(sandbox.commands.run("ls /models").stdout)

OCI images can also be mounted read-only:

mount = Mount(target="/opt/tools", image_url="ubuntu:24.04")

Firecracker mounts must instead use type="erofs" with an S3 object containing a raw EROFS image; it rejects image_url and directory-backed mounts.

Launch from a Dockerfile

Dockerfile direct launch is a supported AKernel SDK capability and will remain available. Its documented strict subset evolves incrementally with production experience; unsupported inputs continue to fail closed. The specific API surface may evolve, with documentation and migration guidance for material changes. It is not a general-purpose Docker build.

FROM supplies only the root filesystem; inherited OCI configuration is not applied. Precheck the context, then pass its launch configuration to Sandbox:

from akernel_sdk import DockerfileLaunch, LocalDockerContext, Sandbox, check_direct_launch
context = LocalDockerContext("Dockerfile", context_dir=".")
if check_direct_launch(context).direct_launchable:
    with Sandbox(dockerfile=DockerfileLaunch(context, run_timeout=300)) as sandbox:
        startup = sandbox.startup_command
        if startup is not None:
            # For a finite CMD/ENTRYPOINT, collect its exit code and output.
            result = startup.wait(timeout=60)
            print(result.exit_code, result.stdout, result.stderr)

startup_command is a CommandHandle for the Dockerfile's background CMD/ENTRYPOINT. It is None when auto_start_cmd=False or no startup command is declared. The handle supports wait(timeout=...) and kill(); construction confirms dispatch, while application readiness requires a separate health check. For long-running services, perform that check instead of waiting for exit during startup.

wait_entrypoint() is exclusive to image launches with inherit_entrypoint=True; calling it on a Dockerfile launch raises RuntimeError, and entrypoint_exit_info is None. See the image launch examples for that path.

RUN, COPY, and ADD run on every launch without a snapshot or cache; unsupported Dockerfiles must be built externally. Read the complete contract, security boundaries, and supported syntax in the Dockerfile launch guide. See the runnable example.

Resources and lifecycle

resources() returns stable NodeInfo values rather than backend objects:

from akernel_sdk import resources

for node in resources():
    print(node.id, node.status, node.capacity, node.allocatable, node.labels)

Accelerators appear under keys such as GPU/l20. Capacity is the total card count and allocatable is the currently free count. ak resources renders the same information as, for example, gpu/l20 1/4. Its status column renders only normal nodes as OK; unavailable states remain explicit as EVICTING, RECOVERING, or TO_BE_DELETED.

Use a context manager or call kill() in a finally block. The SDK does not delete sandboxes during garbage collection or interpreter exit. Cleanup failures propagate and can be retried explicitly; existing workload exceptions are preserved on context exit. Server idle_timeout is a fallback, not a maximum lifetime.

For a named detached sandbox, explicitly delete it when it is no longer needed:

sandbox = Sandbox(name="worker", detached=True)
sandbox.kill()             # closes local clients; remote sandbox remains
Sandbox.delete("worker")   # terminates the named remote sandbox

sandbox.id is the physical ID shown by ak list. get_info() returns a SandboxInfo containing id, state, requested CPU, memory, XPU and storage, and the OCI image when one was configured.

CLI

The ak CLI is installed with the SDK package:

ak resources
ak list
ak list --quiet
ak exec <sandbox-id>
ak exec <sandbox-id> -- /bin/sh
ak delete <sandbox-id> [<sandbox-id> ...]

It uses the same AKERNEL_SERVER_ADDRESS and AKERNEL_TOKEN environment as the Python API.

ak list displays ID, TENANT, and STATUS. Administrator keys can see instances across tenants; tenant keys only see their own instances. ak list --quiet still prints only IDs. A server that omits tenant_id is displayed with - in the tenant column; the CLI does not guess the owner.

ak resources displays per-node CPU, memory, disk, and accelerator resources. DISK is scheduling capacity, DISK ALLOC is allocated capacity, and DISK% is the allocated fraction. Disk values use GiB (G) after converting the API's MiB values. The cluster summary totals disk capacity and allocation. CPU/MEM used values and disk allocation describe the scheduling ledger, not measured process usage or filesystem occupancy. Missing disk values are shown as - and excluded from disk totals.

Examples and tests

Maintained examples are under examples/:

  • basic_usage.py
  • command_stdin.py
  • custom_image.py
  • dockerfile_launch.py
  • failover_reload.py
  • gpu_sandbox.py
  • named_sandbox.py
  • network_policy.py
  • pty.py
  • port_forwarding.py
  • reverse_tunnel.py
  • s3_rootfs_and_mounts.py
  • storage_sandbox.py

Run unit tests without a deployment:

PYTHONPATH=sdk/python \
  python -m unittest discover -s sdk/python/tests/unit -t sdk/python -v

Run the integration suite against a configured deployment:

export AKERNEL_RUN_INTEGRATION=1
PYTHONPATH=sdk/python \
  python -m unittest discover -s sdk/python/tests/integration -t sdk/python -v

Set AKERNEL_TEST_RUNTIME=firecracker and AKERNEL_TEST_IMAGE=ubuntu:24.04 to exercise the same suite, including checkpoint/reload, against a virtio-fs image root. AKERNEL_TEST_IMAGE also accepts a Nydus image reference and enables a check that writes stay private to each sandbox sharing the image. The test image must provide an Ubuntu/Debian userspace with apt-get for the checkpoint test's curl and CA certificate installation. Omit AKERNEL_TEST_IMAGE to test the deployed default EROFS root.

Load and transfer benchmarks live under benchmarks/ and are not part of the default test suite.

Public value types

Type Fields
CommandResult stdout, stderr, exit_code (optional), status, error_code, error_message (optional)
CommandInfo pid, command, running
EntryInfo name, path, type, size, permissions, modified_time
SandboxInfo id, state, cpu, memory, image, xpu, storage_mb
NodeInfo id, status, capacity, allocatable, labels
S3Config endpoint, bucket, object, optional credentials
Mount target, one source, and type
HttpReverseTunnel target, reverse_port, listen_port, connect_timeout
PortRange first, last
NetworkRule action, direction, protocol, peer and sandbox port selectors, priority
TrafficPolicy independent ingress/egress defaults, rules, mode
DNSRule pattern, action
DNSPolicy default_action, rules
NetworkPolicy legacy fields or schema v2 traffic and dns sections
DockerfileLaunch context, auto_start_cmd, run_timeout
DockerContext Abstract Dockerfile and build-context source
DockerContextEntry path, kind, mode
LocalDockerContext Local Dockerfile and context implementation

Metadata

Release files for akernel-sdk 0.2.0rc1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for akernel-sdk 0.2.0rc1
File Size Uploaded Provenance
akernel_sdk-0.2.0rc1.tar.gz 98.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for akernel-sdk 0.2.0rc1
File Interpreter ABI Platform
akernel_sdk-0.2.0rc1-py3-none-any.whl Python 3 none any Details

Total release size: 188.9 kB

Release files / akernel_sdk-0.2.0rc1.tar.gz

Download URL akernel_sdk-0.2.0rc1.tar.gz
Size 98.8 kB
Tags Source
SHA-256 checksum
How to use checksums
1beee8d624291096f833c055f9dc703ff7c132eea97601490fc50ea454180375
BLAKE2b-256 checksum
How to use checksums
902f988fa82708cbd3702b4a8aaa0e45023cf1a19c372db23dd57a9301a0473e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / akernel_sdk-0.2.0rc1-py3-none-any.whl

Download URL akernel_sdk-0.2.0rc1-py3-none-any.whl
Size 90.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
80c64961946e4b8026e99af875d13655e00dc8717404112c24e679917575ecea
BLAKE2b-256 checksum
How to use checksums
1110430ae5d686a46031f7da07ee25364294d86e33e8a068210091a099c274f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Security for akernel-sdk 0.2.0rc1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for akernel_sdk-0.2.0rc1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page