Skip to content
DocsAll docs
Console

Services

Add, change, and remove the services of an environment.

Last updated

On this page

Overview

A service is one program of an environment, run from a container image. The environment always takes the whole list of services: the platform starts new services, restarts changed ones, and stops the ones left out.

FieldRequiredDescription
nameYesThe service name, which is also its host name.
imageYesThe image, for example postgres:16. It is pinned to its digest when stored. A private image of a connected container registry of the team is pulled with its login.
portsNoThe ports the service listens on. Other services and connected workspaces reach it at name:port.
envNoVariables. A value of ${{ secrets.NAME }} refers to a secret of the owner.
mountsNoA folder on the environment disk and the path where the service sees it. Data outside a mount is lost on restart.
commandNoThe command to run. Empty keeps the image's command.
resourcesNovCPU and memory. The default size applies without it.

A removed service keeps its folders on the disk, so adding it again with the same mounts finds its data.

ItemLimit
Name1 to 40 lowercase letters, digits, or -, unique in the environment.
Port1 to 65535.
Variable nameLetters, digits, and _, not starting with a digit.
Mount folderOne name of letters, digits, ., _, and -.
Mount pathAn absolute path without :.

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

Tasks

Add a service

Reason for the task

Run another program in an environment.

Job set-up

Procedure

  1. Add the service. With the SDK, read the current list and send it back with the new service.

    Python
    from kloudlite import Kodekl = Kloudlite()env = kl.environments.get(environment="acme-dev")services = env["services"] + [{"name": "cache", "image": "redis:7", "ports": [6379]}]kl.environments.set_services(environment="acme-dev", services=services)
    TypeScript
    import { Kode, type Service } from "./index.ts";const kl = new Kloudlite();const env = (await kl.environments.get({ environment: "acme-dev" })) as { services: Service[] };const services = [...env.services, { name: "cache", image: "redis:7", ports: [6379] }];await kl.environments.setServices({ environment: "acme-dev", services });
    Go
    kl := kloudlite.New(kloudlite.Options{})env, err := kl.Environments.Get("acme-dev")if err != nil {    log.Fatal(err)}var services []kloudlite.Serviceraw, _ := json.Marshal(env["services"])if err := json.Unmarshal(raw, &services); err != nil {    log.Fatal(err)}services = append(services, kloudlite.Service{Name: "cache", Image: "redis:7", Ports: []int{6379}})if err := kl.Environments.SetServices(kloudlite.SetServices{Environment: "acme-dev", Services: services}); err != nil {    log.Fatal(err)}
    Ruby
    require_relative "kloudlite"kl = Kloudlite::Client.newenv = kl.environments.get(environment: "acme-dev")services = env["services"] + [{ name: "cache", image: "redis:7", ports: [6379] }]kl.environments.set_services(environment: "acme-dev", services: services)
    Java
    var kl = new Kloudlite();// Replaces the list: describe every service that stays, and the new one.var db = new Kloudlite.Service().name("db").image("postgres:16").port(5432)    .env("POSTGRES_PASSWORD", "dev").mount("pgdata", "/var/lib/postgresql/data");var cache = new Kloudlite.Service().name("cache").image("redis:7").port(6379);kl.environments().setServices(new Kloudlite.SetServices().environment("acme-dev").services(db, cache));
    CLI
    # Replaces the list: send every service, including the new one.kode environments set-services acme-dev --services '[  {"name": "db", "image": "postgres:16", "ports": [5432], "env": {"POSTGRES_PASSWORD": "dev"}, "mounts": [{"folder": "pgdata", "path": "/var/lib/postgresql/data"}]},  {"name": "cache", "image": "redis:7", "ports": [6379]}]'

Close-out

  1. Make sure that service_status shows cache as ready.

Change a service

Reason for the task

Change the image, variables, ports, or mounts of a service.

Job set-up

CAUTION

The service restarts and its connections end. Data in its mounted folders is kept.

Procedure

  1. Send the changed service, here db on postgres:17.

    Python
    env = kl.environments.get(environment="acme-dev")services = [{**sv, "image": "postgres:17"} if sv["name"] == "db" else sv for sv in env["services"]]kl.environments.set_services(environment="acme-dev", services=services)
    TypeScript
    const env = (await kl.environments.get({ environment: "acme-dev" })) as { services: Service[] };const services = env.services.map((sv) => (sv.name === "db" ? { ...sv, image: "postgres:17" } : sv));await kl.environments.setServices({ environment: "acme-dev", services });
    Go
    env, err := kl.Environments.Get("acme-dev")if err != nil {    log.Fatal(err)}var services []kloudlite.Serviceraw, _ := json.Marshal(env["services"])if err := json.Unmarshal(raw, &services); err != nil {    log.Fatal(err)}for i := range services {    if services[i].Name == "db" {        services[i].Image = "postgres:17"    }}if err := kl.Environments.SetServices(kloudlite.SetServices{Environment: "acme-dev", Services: services}); err != nil {    log.Fatal(err)}
    Ruby
    env = kl.environments.get(environment: "acme-dev")services = env["services"].map { |sv| sv["name"] == "db" ? sv.merge("image" => "postgres:17") : sv }kl.environments.set_services(environment: "acme-dev", services: services)
    Java
    // Replaces the list: describe every service, with the new image for db.var db = new Kloudlite.Service().name("db").image("postgres:17").port(5432)    .env("POSTGRES_PASSWORD", "dev").mount("pgdata", "/var/lib/postgresql/data");var cache = new Kloudlite.Service().name("cache").image("redis:7").port(6379);kl.environments().setServices(new Kloudlite.SetServices().environment("acme-dev").services(db, cache));
    CLI
    kode environments set-services acme-dev --services '[  {"name": "db", "image": "postgres:17", "ports": [5432], "env": {"POSTGRES_PASSWORD": "dev"}, "mounts": [{"folder": "pgdata", "path": "/var/lib/postgresql/data"}]},  {"name": "cache", "image": "redis:7", "ports": [6379]}]'

Close-out

  1. Make sure that the service shows the new value and is ready.

Remove a service

Reason for the task

Stop a program and take it out of an environment.

Job set-up

Procedure

  1. Send the list without the service. Its folders stay on the disk.

    Python
    env = kl.environments.get(environment="acme-dev")services = [sv for sv in env["services"] if sv["name"] != "cache"]kl.environments.set_services(environment="acme-dev", services=services)
    TypeScript
    const env = (await kl.environments.get({ environment: "acme-dev" })) as { services: Service[] };await kl.environments.setServices({ environment: "acme-dev", services: env.services.filter((sv) => sv.name !== "cache") });
    Go
    env, err := kl.Environments.Get("acme-dev")if err != nil {    log.Fatal(err)}var services []kloudlite.Serviceraw, _ := json.Marshal(env["services"])if err := json.Unmarshal(raw, &services); err != nil {    log.Fatal(err)}kept := services[:0]for _, sv := range services {    if sv.Name != "cache" {        kept = append(kept, sv)    }}if err := kl.Environments.SetServices(kloudlite.SetServices{Environment: "acme-dev", Services: kept}); err != nil {    log.Fatal(err)}
    Ruby
    env = kl.environments.get(environment: "acme-dev")kl.environments.set_services(environment: "acme-dev", services: env["services"].reject { |sv| sv["name"] == "cache" })
    Java
    // Replaces the list: describe every service that stays.var db = new Kloudlite.Service().name("db").image("postgres:16").port(5432)    .env("POSTGRES_PASSWORD", "dev").mount("pgdata", "/var/lib/postgresql/data");kl.environments().setServices(new Kloudlite.SetServices().environment("acme-dev").services(db));
    CLI
    kode environments set-services acme-dev --services '[  {"name": "db", "image": "postgres:16", "ports": [5432], "env": {"POSTGRES_PASSWORD": "dev"}, "mounts": [{"folder": "pgdata", "path": "/var/lib/postgresql/data"}]}]'

Close-out

  1. Make sure that the service is no longer in services.

Troubleshooting

SymptomPossible causeCorrective action
400 naming a field of a serviceA name, port, variable, or mount breaks the rules in Overview.Correct the field.
422 with `DB_PASSWORD` is not a secret of acmeThe secret does not exist for the environment's owner.Set the secret.
422 with image not found in its registryThe registry does not hold the image or tag.Correct the image name or tag.
409 with cpu: 8 of 8 in use; request more under QuotaThe service does not fit in the quota.Remove an unused service, or request more quota.
409 with cache is intercepted by ws-…; release the intercept before removing the serviceA workspace intercepts the service.Release the intercept, then remove the service.