Init Tasks, Startup Files, and Tabs

Init tasks

Init tasks are shell scripts that run inside the playground machines during startup. They are the primary way to turn a generic base image into your environment: install packages, clone repositories, start services, generate data. The playground shows a loading screen until all init tasks complete, so the user always lands in a fully provisioned environment (unless they close the loading modal).

initTasks is a map of named tasks:

manifest.yaml
kind: playground
name: web-dev-lab
title: Web Dev Lab
playground:
  machines:
    - name: dev-01
      users:
        - name: laborant
          default: true
      drives:
        - source: docker
          mount: /
      network:
        interfaces:
          - network: local
  initTasks:
    init_fetch_app:
      init: true
      machine: dev-01
      user: laborant
      timeout_seconds: 120
      run: |
        git clone https://github.com/example/app.git ~/app

    init_start_services:
      init: true
      machine: dev-01
      needs:
        - init_fetch_app
      timeout_seconds: 180
      run: |
        cd /home/laborant/app && docker compose up -d
  accessControl:
    canList:
      - owner
    canRead:
      - owner
    canStart:
      - owner

Field by field:

  • init: true marks the task as an init task (executed once, at startup).
  • machine - which VM the task runs on.
  • user - the user to run the script as; defaults to root.
  • run - the script itself (executed with bash).
  • needs - names of tasks that must complete first; dependencies form a graph, so you can fan out and join provisioning steps across machines.
  • timeout_seconds - defaults to 60 seconds; set it generously for anything that touches the network or a package manager.

⚠️ A failing or timed-out init task keeps the playground stuck on the loading screen. Test your scripts by starting the playground and watching the task progress - labctl playground tasks <play-id> shows the status of each task from the command line:

NAME                 MACHINE  STATUS     INIT  HELPER
init_fetch_app       dev-01   completed  true  false
init_start_services  dev-01   completed  true  false

Parameterized playgrounds

Init tasks can be made conditional on init conditions - user-supplied parameters requested at start time:

  initConditions:
    values:
      - key: k8s_flavor
        default: k3s
        options:
          - k3s
          - kubeadm
  initTasks:
    init_install_kubeadm:
      init: true
      machine: dev-01
      conditions:
        - key: k8s_flavor
          value: kubeadm
      run: |
        ...

When starting such a playground, the UI prompts for the values, and with labctl they are passed explicitly:

labctl playground start web-dev-lab-<suffix> -i k8s_flavor=kubeadm

Tasks whose conditions don't match the chosen values are simply skipped (they won't even appear in the task list).

Startup files and welcome messages

For small tweaks - shell profiles, config files, motd-style notes - a full init task is overkill. Each machine can declare up to 10 startup files that are placed into the filesystem before the first boot completes:

  machines:
    - name: dev-01
      drives:
        - source: golang
          mount: /
      startupFiles:
        - path: /home/laborant/.bashrc
          append: true
          content: |
            export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
            export GOPATH=$HOME/go/
        - path: /etc/app/config.yaml
          owner: laborant
          mode: "600"
          content: |
            environment: playground
  • path must be absolute; missing parent directories are created.
  • append: true adds to an existing file instead of replacing it - the go-to for .bashrc and similar.
  • owner (user, user:group, or numeric IDs) and mode (octal, without the leading zero - e.g. "600") default to root-owned "644".

Startup files is the only reliable way to customze login shell behavior (e.g., by placing something in the ~/.bashrc file). Init tasks run already after the machine is booted, and technically, the user may acquire a shell before all init tasks complete.

Welcome messages

The first thing a user sees in a terminal is the machine's welcome message. It's configured per user and is well worth the effort - a good welcome message explains what the machine is, what's installed, and where to start:

      users:
        - name: laborant
          default: true
          welcome: |
            This is a development machine with Go, Docker, and kubectl preinstalled.
            The demo app lives in ~/app - run `make help` to see what it can do.

Set welcome: '-' to suppress the message entirely (useful for secondary machines).

Tabs

Tabs define the panes a user sees on the playground page. If the manifest doesn't mention tabs at all, the defaults are sensible: an IDE tab (for most base playgrounds; flexbox defaults to terminals only) plus one terminal per (SSH-enabled) machine, and a Kubernetes explorer for Kubernetes playgrounds.

Declaring your own tabs list replaces the defaults entirely, giving you full control over the layout and order:

manifest.yaml
kind: playground
name: web-dev-lab
title: Web Dev Lab
playground:
  tabs:
    - kind: ide
      machine: dev-01
    - kind: http-port
      name: Web UI
      machine: dev-01
      number: 8080
    - kind: terminal
      machine: dev-01
    - kind: web-page
      name: Docs
      url: https://docs.example.com
  machines:
    - name: dev-01
      users:
        - name: laborant
          default: true
      drives:
        - source: golang
          mount: /
      network:
        interfaces:
          - network: local
  accessControl:
    canList:
      - owner
    canRead:
      - owner
    canStart:
      - owner

The available tab kinds:

KindWhat it showsKey fields
terminalA shell on a machine (the default kind)machine
ideA web-based VS Code-style IDEmachine
http-portAn application port of a machine, rendered in an iframemachine, number (port), name, optional tls: true for HTTPS backends
web-pageAn external web pagename, url
kexpThe Kubernetes Explorer-

A playground can have from 1 to 10 tabs. Machines with noSSH: true never get terminal tabs.

💡 An http-port tab is a convenient permanent variant of exposing HTTP ports: the application must listen on the machine's main interface (or 0.0.0.0) for the tab to work. Users can still expose additional ports ad-hoc while the playground is running.