Skip to content

Configuration

Skyward does not read a configuration file. The control plane target is resolved for each client or CLI call.

Control plane resolution

The resolution order is:

  1. an explicit url or --url;
  2. SKYWARD_URL;
  3. the daemon at http://127.0.0.1:17590.

Compute starts a daemon at that address when none answers, printing no server is running, starting it now. The daemon detaches, so it outlives the process that started it and goes on reconciling the machines it bought. Stop it with sky server stop. The CLI never starts one: it reports that nothing answers.

A daemon that runs a different version of Skyward than the client is warned about and used: the same routes may carry other wire types, and a type the two disagree on fails where it is read. sky.Options(strict_version=True) refuses it instead, before a machine is bought; stop it and let the client start one, or point the client at a daemon on its version.

Passing database= to Compute runs the control plane inside the current process over that file instead of reaching a daemon. sky server start --database gives a daemon its own. Either way the default is ~/.skyward/skyward.sqlite, and a database is ignored when a URL is given.

import skyward as sky

# The daemon at 127.0.0.1:17590, started here if nobody has.
with sky.Compute(provider=sky.Container()) as compute:
    result = train(data) >> compute

# The control plane in this process, over a database of its own.
with sky.Compute(provider=sky.Container(), database="/tmp/experiment.sqlite") as compute:
    result = train(data) >> compute

# Remote daemon.
with sky.Compute(provider=sky.AWS(), url="http://127.0.0.1:17590") as compute:
    result = train(data) >> compute

The Compute client and the CLI use the same resolution rules. Inspect the resolved values with:

sky config path
sky config show
sky config validate

Provider accounts

Provider accounts resolve credentials in the client process. A provider descriptor contains the provider kind, its non-secret configuration, credentials, and an optional account name. When a compute starts, the descriptor registers the named account with the daemon if it does not exist. Credentials are not returned by provider read operations.

provider = sky.AWS(name="production", region="us-east-1")

with sky.Compute(provider=provider, accelerator="A100") as compute:
    train(data) >> compute

Use a distinct name for multiple accounts of the same provider kind. Provider configuration belongs to the account, not to a global singleton.

Compute configuration

The public configuration objects are composed at the Compute boundary:

  • Spec describes one provider and hardware alternative;
  • Options controls timeouts, retries, health checks, and autoscaling;
  • Executor controls task execution on each node;
  • Image, Volume, and Port describe the node environment and local tunnels.

See Compute and task dispatch for the full Python surface and Cloud providers for account credentials and offer caching.

skyward.Provider

Bases: Struct

A provider account: which cloud, and what it takes to log in.

Attributes:

Name Type Description
name str

The account's alias, and the identity of the provider row. Two accounts of the same kind coexist by having two names. Empty is the kind itself.

kind class-attribute

name = '' class-attribute instance-attribute

__post_init__()

skyward.Image

Bases: Struct

The environment a node builds before it runs anything.

The base, the interpreter, the packages and where they resolve from. What the user shipped from their own machine is not here — includes is packed into a blob client-side and only its hash travels, because a spec is written to the compute row and served back by the API.

base = None class-attribute instance-attribute

python = None class-attribute instance-attribute

pip = () class-attribute instance-attribute

apt = () class-attribute instance-attribute

pip_indexes = () class-attribute instance-attribute

env = field(default_factory=dict) class-attribute instance-attribute

shell_vars = field(default_factory=dict) class-attribute instance-attribute

includes = () class-attribute instance-attribute

excludes = () class-attribute instance-attribute

includes_sha256 = None class-attribute instance-attribute

The user-code tarball, once the client has built it and put it in the blob store. includes/excludes are the client's inputs; this is what the node reads.

metrics = None class-attribute instance-attribute

What the node measures about itself: :data:READINGS when None, exactly the list otherwise.

A :data:Reading is served by the node's own collector, a :class:MetricSpec by a loop of its own. A name may appear once, whichever kind it is.

bootstrap_timeout = 900 class-attribute instance-attribute

skyward = 'auto' class-attribute instance-attribute

warm = False class-attribute instance-attribute

Whether a machine that finished bootstrapping is kept as a boot image.

Off because what it creates is never removed: an AMI holds a snapshot that bills for its storage until it is deregistered, and nothing here deregisters it. Turning it on is taking that on. What is created carries :meth:content_hash as a tag, on the image and on the snapshot behind it, so it can be found again and removed. Only providers that can snapshot a running machine honor it.

mutable = False class-attribute instance-attribute

Whether the image of a compute that is up may be changed, best-effort.

Only :data:MUTABLE may change, and a node that is ready with another image is sent through bootstrapping again and comes back ready. A package removed from pip is not promised to leave the machine. A compute built without it keeps the contract it always had: a different image is a different compute.

MUTABLE = frozenset({'pip', 'pip_indexes', 'includes', 'excludes', 'includes_sha256'}) class-attribute

The fields a PATCH may change on a mutable image.

__post_init__()

content_hash(source)

Name the environment a bootstrapped machine ends up in.

Covers what the bootstrap installs — the base, the interpreter, the packages and the indexes they are resolved from — together with source, which is what stands in for a skyward version now that a node installs whatever the daemon is running.

Left out is everything the bootstrap re-applies on every boot: the exports, the shell vars, the metric commands, and the user code, which is synced per run. Folding those in would split the images over changes that cost nothing to redo.

Parameters:

Name Type Description Default
source str

:attr:skyward.server.application.source.Source.argument — what follows uv pip install. Never a locally built wheel: its bytes change with every edit, so a name derived from it would outlive what it named.

required

Returns:

Type Description
str

Twelve hex characters — long enough to name an image, short enough to read in one.

digest()

Name exactly this image, every field included.

A node reports the digest of the image it materialized, and the reconciler compares it with the spec's to tell when the spec asks for another one. Unlike :meth:content_hash it is not about warm images, and it includes what the bootstrap re-applies: the exports, the shell vars, the metrics and the includes.

Returns:

Type Description
str

The sha256 of the image's deterministic JSON encoding, in full.