Skip to content
DocsAll docs
Console

Environments

Create, start, stop and delete environments, the running services that workspaces develop against.

Last updated

On this page

Description and operation

General

An environment is a set of services that run together, for example an API, a database and a cache. It gives workspaces the real dependencies of an application, so code is developed against them, not against mocks.

A workspace connects to an environment through a session. It then reaches each service by name, for example postgres://db:5432. An intercept sends a service's traffic to a workspace, so a change runs against the rest of the environment before it is merged.

Each environment has its own isolated network, and one disk shared by its services. It belongs to you or to a team; many sessions can connect to one environment.

PartDescription
ServicesThe programs of the environment, each from an image. See Services.
DiskHolds the data of services that mount a folder. Its size is quota_gb. It is kept while the environment is stopped.
NamesEach service is reachable by its name on the ports that it lists.

To do these tasks with an AI agent, see Environments and intercepts with an agent.

States

stateMeaning
creatingStarting services, or waiting for capacity.
runningRunning. service_status shows whether each service is ready.
stoppedNot running. The disk is kept.
errorThe environment failed.
deletedShown only in the response to delete.

Lifecycle

  • Idle stop. An environment runs while a connected session is awake. After 5 minutes with none awake, it stops and sets idle_since. Using a connected session starts it again; an environment that you stopped stays stopped.
  • Time to live. The platform deletes an environment that is unused for ttl_secs (600 to 2592000). An environment with a connected session, awake or idle, is in use and is never deleted.
  • Delete. The services and the disk are removed. Connected sessions are disconnected, not ended. Snapshots stay.
  • Interruption. If the machine that holds a running environment fails, start and clone return 409 until it returns. You can clone a snapshot into a new environment.

Clone

A clone is a new environment that starts with a copy of an environment. A clone is not a snapshot. The copy is independent: changes in the clone do not change the source, and changes in the source do not change the clone. The clone call answers 202 and starts the new environment at once.

CloneWhat the copy holds
Present stateThe live data of all services at the time of the request, the service list and the disk size of the source
From a snapshotThe data and the definition that a ready snapshot of the environment froze. See Snapshot types.

A clone of the present state copies the live data of the source to a new disk. It has the owner and the region of the source. It has no based_on, because the platform makes no snapshot for it. It is refused with 409 while the source is interrupted.

A clone from a snapshot moves the expiry of the snapshot to a full default lifetime from now. With snapshot, the owner is the owner of the snapshot, you, or a team that you belong to.

A clone does not take the variables, the sessions, the intercepts or the time to live of its source. The new environment has no connected session. A clone counts as a new environment against the quota of the owner.

ParameterRequiredDescription
toYesThe name of the new environment. Use team/name to give the owner with snapshot.
snapshotNoThe id of a ready snapshot of this environment. Without it, the clone copies the present state.
servicesNoOnly with snapshot. Replaces the frozen service list. An empty list means the list of the snapshot.
quota_gbNoOnly with snapshot. The disk size. Absent means the size that the snapshot froze.
ttl_secsNoAPI only. The time to live of the new environment, 600 to 2592000 seconds.

To do these tasks with an AI agent, see Agentic workflows – Volumes.

Restore

A restore puts the same environment back to one of its own snapshots. You name the snapshot by its id, and the snapshot must be ready. A restore moves the expiry of the snapshot to a full default lifetime from now. To make a new environment from a snapshot, clone it with snapshot_id.

The data and the service list return to the snapshot. The disk size becomes the size that the snapshot froze. The platform stops the services, puts back the data and starts the services of the snapshot.

The answer lists what the restore does to each service, by name.

FieldDescription
rolled_backServices in both lists. Their data returns to the snapshot.
addedServices that the snapshot has and the environment does not.
removedServices that the environment has and the snapshot does not.
discardedServices that you named in discard. Their data is lost.

A service can keep data that the snapshot does not have. The restore is then refused with 409 and a list lost. To accept the loss, name exactly those services in discard.

ParameterRequiredDescription
snapshotYesThe id of a ready snapshot of this environment.
discard (CLI --discard, repeated)NoThe services whose data you agree to lose. Name exactly the services in lost.

A restore counts the cpu and memoryGb of the services that it adds against the quota. It is refused when the disk of the owner is already above diskGb. It is also refused while a service that it removes has an intercept.

Quota and limits

An environment counts its disk, and the vCPU and memory of its running services, against the owner's quota.

ItemLimit
Name1 to 40 lowercase letters, digits or -. Start with a letter, end with a letter or digit, no --.
Reserved namesws, env, api, admin, system, kl, bld-*, and names that look like an id.
Disk1 to 500 GB. The default is 20 GB.

Tasks

Create an environment

Reason for the task: Run the services of an application for workspaces to develop against.

Job set-up

Procedure

  1. Create the environment with its services.

    Python
    from kloudlite import Kloudlitekl = Kloudlite()env = kl.environments.create(    environment="acme-dev",    services=[{"name": "db", "image": "postgres:16", "ports": [5432],               "env": {"POSTGRES_PASSWORD": "dev"},               "mounts": [{"folder": "pgdata", "path": "/var/lib/postgresql/data"}]}],    quota_gb=20,)print(env["id"], env["state"])
    TypeScript
    import { Kloudlite } from "./index.ts";const kl = new Kloudlite();const env = (await kl.environments.create({  environment: "acme-dev",  services: [{    name: "db", image: "postgres:16", ports: [5432],    env: { POSTGRES_PASSWORD: "dev" },    mounts: [{ folder: "pgdata", path: "/var/lib/postgresql/data" }],  }],  quotaGb: 20,})) as Record<string, unknown>;console.log(env.id, env.state);
    Go
    kl := kloudlite.New(kloudlite.Options{})env, err := kl.Environments.Create(kloudlite.CreateEnvironment{    Environment: "acme-dev",    Services: []kloudlite.Service{{        Name:   "db",        Image:  "postgres:16",        Ports:  []int{5432},        Env:    map[string]string{"POSTGRES_PASSWORD": "dev"},        Mounts: []kloudlite.Mount{{Folder: "pgdata", Path: "/var/lib/postgresql/data"}},    }},    QuotaGb: 20,})if err != nil {    log.Fatal(err)}fmt.Println(env["id"], env["state"])
    Ruby
    require_relative "kloudlite"kl = Kloudlite::Client.newenv = kl.environments.create(  environment: "acme-dev",  services: [{ name: "db", image: "postgres:16", ports: [5432],               env: { "POSTGRES_PASSWORD" => "dev" },               mounts: [{ folder: "pgdata", path: "/var/lib/postgresql/data" }] }],  quota_gb: 20)puts env.values_at("id", "state").join(" ")
    Java
    var kl = new Kloudlite();System.out.println(kl.environments().create(new Kloudlite.CreateEnvironment()    .environment("acme-dev")    .services(new Kloudlite.Service()        .name("db").image("postgres:16").port(5432)        .env("POSTGRES_PASSWORD", "dev")        .mount("pgdata", "/var/lib/postgresql/data"))    .quotaGb(20)));
    CLI
    kloudlite environments create acme-dev --quota-gb 20 --services '[  {"name": "db", "image": "postgres:16", "ports": [5432], "env": {"POSTGRES_PASSWORD": "dev"}, "mounts": [{"folder": "pgdata", "path": "/var/lib/postgresql/data"}]}]'

Close-out

  1. Wait until state is running and every service in service_status is ready.

Show the status of an environment

Reason for the task: Check whether an environment and its services run, and why not.

Job set-up

Procedure

  1. Show the environment. kloudlite environments list lists them all.

    Python
    env = kl.environments.get(environment="acme-dev")print(env["state"], env.get("service_status"), env.get("idle_since"))
    TypeScript
    const env = (await kl.environments.get({ environment: "acme-dev" })) as Record<string, unknown>;console.log(env.state, env.service_status, env.idle_since);
    Go
    env, err := kl.Environments.Get("acme-dev")if err != nil {    log.Fatal(err)}fmt.Println(env["state"], env["service_status"], env["idle_since"])
    Ruby
    env = kl.environments.get(environment: "acme-dev")puts env.values_at("state", "service_status", "idle_since").inspect
    Java
    System.out.println(kl.environments().get("acme-dev"));
    CLI
    kloudlite environments get acme-dev

Close-out

  1. Make sure that state and service_status are what you expect. If not, see Troubleshooting.

Start an environment

Reason for the task: Run a stopped environment.

Job set-up

Procedure

  1. Start the environment.

    Python
    kl.environments.start(environment="acme-dev")
    TypeScript
    await kl.environments.start({ environment: "acme-dev" });
    Go
    if err := kl.Environments.Start("acme-dev"); err != nil {    log.Fatal(err)}
    Ruby
    kl.environments.start(environment: "acme-dev")
    Java
    kl.environments().start("acme-dev");
    CLI
    kloudlite environments start acme-dev

Close-out

  1. Wait until state is running and every service is ready.

Stop an environment

Reason for the task: Release the compute of an environment and keep its data.

Job set-up

CAUTION

All services stop. Connected workspaces lose access to them until the environment starts again.

Procedure

  1. Stop the environment.

    Python
    kl.environments.stop(environment="acme-dev")
    TypeScript
    await kl.environments.stop({ environment: "acme-dev" });
    Go
    if err := kl.Environments.Stop("acme-dev"); err != nil {    log.Fatal(err)}
    Ruby
    kl.environments.stop(environment: "acme-dev")
    Java
    kl.environments().stop("acme-dev");
    CLI
    kloudlite environments stop acme-dev

Close-out

  1. Make sure that state is stopped. A warning in the response means that the machine of the environment is down.

Set the time to live

Reason for the task: Delete an environment automatically after a period without use.

Job set-up

WARNING

When the time ends, the services and the disk are deleted. Take a snapshot of data that you want to keep.

Procedure

  1. Set ttl_secs from 600 to 2592000. Set it to no value (None, null, nil) to clear it.

    Python
    kl.environments.set_ttl(environment="acme-dev", ttl_secs=86400)
    TypeScript
    await kl.environments.setTtl({ environment: "acme-dev", ttlSecs: 86400 });
    Go
    ttl := int64(86400)if _, err := kl.Environments.SetTTL("acme-dev", &ttl); err != nil {    log.Fatal(err)}
    Ruby
    kl.environments.set_ttl(environment: "acme-dev", ttl_secs: 86400)
    Java
    kl.environments().setTtl("acme-dev", 86400L);
    CLI
    kloudlite environments set-ttl acme-dev --ttl-secs 86400

Close-out

  1. Make sure that expires_at shows a time, or no value if you cleared it.

Delete an environment

Reason for the task: Remove an environment that you no longer need.

Job set-up

WARNING

The services and the disk are deleted. Data that is not in a snapshot is lost.

Procedure

  1. Delete the environment.

    Python
    kl.environments.delete(environment="acme-dev")
    TypeScript
    await kl.environments.delete({ environment: "acme-dev" });
    Go
    if err := kl.Environments.Delete("acme-dev"); err != nil {    log.Fatal(err)}
    Ruby
    kl.environments.delete(environment: "acme-dev")
    Java
    kl.environments().delete("acme-dev");
    CLI
    kloudlite environments delete acme-dev

Close-out

  1. Make sure that the environment is no longer listed. A warning in the response names a session that was not disconnected.

List environments

Reason for the task: Find the environments that you and your teams can use.

Job set-up

Procedure

  1. Show the list. Pass an owner to list the environments of one handle or team.

    Python
    print(kl.environments.list())
    TypeScript
    console.log(await kl.environments.list());
    Go
    list, err := kl.Environments.List("")if err != nil {    log.Fatal(err)}fmt.Println(list)
    Ruby
    p kl.environments.list
    Java
    System.out.println(kl.environments().list(null));
    CLI
    kloudlite environments list

Close-out

  1. Make sure that each environment that you expect is listed.

Clone an environment

Reason for the task: Make a new environment from the present state of an environment, or from one of its snapshots.

Job set-up

Procedure

  1. Clone the present state of the environment. Use a plain name for to.

    Python
    new = kl.environments.clone(environment="acme/dev", to="dev-copy")print(new["id"], new["state"])
    TypeScript
    const created = (await kl.environments.clone({ environment: "acme/dev", to: "dev-copy" })) as Record<string, unknown>;console.log(created.id, created.state);
    Go
    created, err := kl.Environments.Clone(kloudlite.CloneEnvironment{Environment: "acme/dev", To: "dev-copy"})if err != nil {    log.Fatal(err)}fmt.Println(created["id"], created["state"])
    Ruby
    created = kl.environments.clone(environment: "acme/dev", to: "dev-copy")puts created.values_at("id", "state").join(" ")
    Java
    // The SDK returns the answer as raw JSON. Read the fields with your own JSON library.String created = kl.environments().clone(new Kloudlite.CloneEnvironment().environment("acme/dev").to("dev-copy"));System.out.println(created);
    CLI
    kloudlite environments clone acme/dev --to dev-copy
  2. Give the id of a snapshot in snapshot in place of step 1, to clone that snapshot. Add quota_gb to change the disk size.

    Python
    new = kl.environments.clone(environment="acme/dev", to="dev-before-migration", snapshot="env-1f2e3d4c5b6a7988-8a7b6c5d", quota_gb=40)print(new["id"], new["state"])
    TypeScript
    const created = (await kl.environments.clone({  environment: "acme/dev",  to: "dev-before-migration",  snapshot: "env-1f2e3d4c5b6a7988-8a7b6c5d",  quotaGb: 40,})) as Record<string, unknown>;console.log(created.id, created.state);
    Go
    created, err := kl.Environments.Clone(kloudlite.CloneEnvironment{    Environment: "acme/dev",    To:          "dev-before-migration",    Snapshot:    "env-1f2e3d4c5b6a7988-8a7b6c5d",    QuotaGb:     40,})if err != nil {    log.Fatal(err)}fmt.Println(created["id"], created["state"])
    Ruby
    created = kl.environments.clone(environment: "acme/dev", to: "dev-before-migration", snapshot: "env-1f2e3d4c5b6a7988-8a7b6c5d", quota_gb: 40)puts created.values_at("id", "state").join(" ")
    Java
    String created = kl.environments().clone(new Kloudlite.CloneEnvironment()    .environment("acme/dev")    .to("dev-before-migration")    .snapshot("env-1f2e3d4c5b6a7988-8a7b6c5d")    .quotaGb(40));System.out.println(created);
    CLI
    kloudlite environments clone acme/dev --to dev-before-migration --snapshot env-1f2e3d4c5b6a7988-8a7b6c5d --quota-gb 40

Close-out

  1. Wait until the state of the new environment is running.
  2. Examine service_status of the new environment. Make sure that each service is ready.

Restore an environment from a snapshot

Reason for the task: Put an environment back to the services and the data of one of its snapshots.

Job set-up

WARNING

The restore replaces the present data of the environment with the data of the snapshot. Take a snapshot first to keep the present state.

Procedure

  1. Restore the environment. Use the id of the snapshot for snapshot.

    Python
    plan = kl.environments.restore(environment="acme/dev", snapshot="env-1f2e3d4c5b6a7988-8a7b6c5d")print(plan)
    TypeScript
    const plan = await kl.environments.restore({ environment: "acme/dev", snapshot: "env-1f2e3d4c5b6a7988-8a7b6c5d" });console.log(plan);
    Go
    // Environments.Restore keeps only the error, so read the answer with a raw request.var plan anyif err := kl.Request("POST", "/v1/environments/dev/restore", kloudlite.RequestOptions{    Query: url.Values{"owner": {"acme"}},    Body:  map[string]any{"snapshot_id": "env-1f2e3d4c5b6a7988-8a7b6c5d", "discard": []string{}},    Out:   &plan,}); err != nil {    log.Fatal(err)}fmt.Println(plan)
    Ruby
    plan = kl.environments.restore(environment: "acme/dev", snapshot: "env-1f2e3d4c5b6a7988-8a7b6c5d")p plan
    Java
    String plan = kl.environments().restore(new Kloudlite.RestoreEnvironment().environment("acme/dev").snapshot("env-1f2e3d4c5b6a7988-8a7b6c5d"));System.out.println(plan);
    CLI
    kloudlite environments restore acme/dev --snapshot env-1f2e3d4c5b6a7988-8a7b6c5d
  2. If the call returns 409 with a list lost, restore again. Name those services in discard, or repeat --discard SERVICE in the CLI.

    Python
    plan = kl.environments.restore(environment="acme/dev", snapshot="env-1f2e3d4c5b6a7988-8a7b6c5d", discard=["redis"])print(plan)
    TypeScript
    const plan = await kl.environments.restore({ environment: "acme/dev", snapshot: "env-1f2e3d4c5b6a7988-8a7b6c5d", discard: ["redis"] });console.log(plan);
    Go
    err := kl.Environments.Restore(kloudlite.RestoreEnvironment{Environment: "acme/dev", Snapshot: "env-1f2e3d4c5b6a7988-8a7b6c5d", Discard: []string{"redis"}})if err != nil {    log.Fatal(err)}
    Ruby
    plan = kl.environments.restore(environment: "acme/dev", snapshot: "env-1f2e3d4c5b6a7988-8a7b6c5d", discard: ["redis"])p plan
    Java
    String plan = kl.environments().restore(new Kloudlite.RestoreEnvironment().environment("acme/dev").snapshot("env-1f2e3d4c5b6a7988-8a7b6c5d").discard("redis"));System.out.println(plan);
    CLI
    kloudlite environments restore acme/dev --snapshot env-1f2e3d4c5b6a7988-8a7b6c5d --discard redis

Close-out

  1. Read rolled_back, added, removed and discarded in the answer.
  2. Wait until the state of the environment is running.

Troubleshooting

SymptomPossible causeCorrective action
422 with name must be 1-40 lowercase letters…The name breaks the naming rules.Use a name that follows Quota and limits.
409 with an environment named "acme-dev" already exists hereThe owner already has an environment with this name.Use another name, or delete the old environment.
409 with environments: 5 of 5 in use; request more under QuotaThe owner's quota is used up. The first word names the dimension, for example diskGb or cpu.Delete unused environments, or request more quota.
404 with not foundThe environment does not exist, or you are not in its team.Check the name. Use team/name for a team environment.
409 with is ambiguous; qualify it as owner/nameYou and a team both have an environment with this name.Use team/name.
409 with environment is interrupted: its node is downThe machine that holds the environment failed.Wait for it to resume, or clone a snapshot into a new environment.
409 with the source environment is interrupted: its node is down, …A clone of the present state needs a running machine for the source.Wait until it returns, or clone a snapshot as in task 008, step 2.
400 with `owner`, `services` and `quota_gb` apply only with `snapshot_id`You gave these fields in a clone of the present state.Remove them, or give a snapshot.
400 with an environment needs at least one service; this snapshot froze none, so pass `services` The snapshot froze no services.Give services in the clone.
400 with this snapshot was cut from a workspace; use POST /v1/workspaces/restoreYou cloned an environment from a workspace snapshot.Restore a workspace from it.
404 with not found in a clone or a restoreThe snapshot does not exist, is not yours, is not ready, or is not of this environment.Use an id with the phase ready from the history.
409 with these services keep data that the snapshot does not have; name them in `discard` to lose itThe restore drops a service or a folder that keeps data.Read lost. To accept the loss, name those services in discard.
422 with `discard` names services that lose nothing: redisdiscard names a service that loses no data.Keep only the names in lost.
422 with snapshot predates saved services; clone it into a new environment insteadThe snapshot froze no service list.Clone the snapshot into a new environment.
409 with api is intercepted by …; release the intercept before restoringA service that the restore removes has an intercept.Remove the intercept, then restore again.
409 with not ready yet: no volume for this workspaceThe environment has no disk yet.Wait until it is ready, then restore again.
state stays creating and placed is NoCapacityThe platform has no capacity right now.Wait, or stop an environment that you do not need.