Workspace Automation

A Workspace is the primary Linux environment for development. It typically contains:

Crafting provides highly customizable automation when a workspace starts, as well as automated management of background processes and cron jobs.

Start Sequence

When a workspace starts — either when newly created or restarted for any reason — the following Start Sequence (also called Workspace Setup and Automation or Setup Tasks) is executed in order:

  1. Pull images needed for the workspace, including base snapshots and Web IDE images.
  2. Essential System Config: A non-customizable step where Crafting ensures its own functionality is properly configured.
  3. Apply home and personal snapshots (if specified and available).
  4. Inject files defined in the workspace definition.
  5. Run /etc/sandbox.d/setup script (if present and executable).
  6. Run ~/.sandbox/setup script (if present and executable).
  7. Launch global (system-level) daemons.
  8. Check out source code from all defined Checkouts.
  9. Run post-checkout hooks from all Checkouts.
  10. Run build hooks from all Checkouts.
  11. Launch daemons and activate jobs from all Checkouts.
  12. Run /etc/sandbox.d/post-setup script (if present and executable).

Some tasks only run once when the workspace is first created (e.g. applying snapshots), while others run every time the workspace starts (e.g. file injections, setup, and post-setup scripts).

Checkout tasks and hooks run once by default, unless the workspace is in Auto Mode. Daemons and jobs are always launched regardless of whether the build hook succeeds or fails, because they are automatically managed and restarted on failure.

Checkouts

A workspace can automatically check out defined code repositories, build the code, and launch daemons, so a sandbox runs a fully functioning application end-to-end when it becomes ready — with no manual steps required.

One or more Checkout entries can be added to the checkouts list of a workspace definition. For example:

workspaces:
- name: example
  checkouts:
  - path: foo
    repo:
      git: git@github.com:example-org/foo
    manifest:
      overlays:
      - inline:
          hooks:
            post-checkout:
              cmd: |
                npm install
            build:
              cmd: |
                npm run build
          daemons:
            server:
              run:
                cmd: npm start

Field reference:

Repository

Only git repositories are currently supported. The repository can be defined in the following formats:

SSH protocol (recommended for pull and push access):

repo:
  git: git@github.com:example-org/foo

This uses the workspace's Managed SSH Keypair for authentication.

HTTPS protocol:

repo:
  git: https://github.com/example-org/foo

Always works with public repositories, but does not allow pushing changes and will fail for private repositories.

GitHub App (when the org has the GitHub App enabled):

repo:
  github:
    org: example-org
    repo: foo

The workspace automatically constructs the origin git remote as https://github.com/example-org/foo and uses the GitHub App to authenticate. See GitHub App for details.

Checkout Alternative Versions

By default, code is checked out from the default branch. To specify a different version, use version_spec:

checkouts:
- path: foo
  repo:
    git: git@github.com:example-org/foo
  version_spec: next

The value of version_spec can be a branch name, a tag name, or a commit ID.

Controlling Checkout History Depth

For large repositories, pulling the full history is often unnecessary and slow. Use the history field to limit what is fetched:

# By depth
checkouts:
- path: foo
  repo:
    git: git@github.com:example-org/foo
  history:
    depth: 1

# By date
checkouts:
- path: foo
  repo:
    git: git@github.com:example-org/foo
  history:
    since: '2025-10-12'

The depth field maps to git's --depth flag, and since maps to --shallow-since.

Recursive Checkout

Unlike the default git clone behavior, submodules are checked out recursively by default, because workspaces automate end-to-end builds and typically require the full source tree. To disable this:

checkouts:
- path: foo
  repo:
    git: git@github.com:example-org/foo
  disable_recursive_checkout: true

Manifest

The Manifest (specified by manifest) defines the automation details for a particular checkout, including:

Loading the Manifest

The manifest is defined using one or more overlays. If no overlays are specified, the workspace tries to load the manifest from .sandbox/manifest.yaml at the root of the source tree. When overlays are specified, .sandbox/manifest.yaml is skipped, and all items under overlays are merged in the order they are defined.

Full Manifest Example

env:
- FOO=BAR

hooks:
  post-checkout:
    cmd: ./post-checkout.sh
    dir: scripts
    env:
    - FOO=my${BAR}
  build:
    cmd: ./build.sh
    dir: scripts
    env:
    - FOO=my${BAR}

daemons:
  server:
    run:
      cmd: ./server
      dir: out
      env:
      - NODE_ENV=dev
  monitor:
    run:
      cmd: ./monitor
      dir: out

jobs:
  housekeeper:
    run:
      cmd: ./housekeep
      dir: out
    schedule: '* * * * *'

The top-level env defines environment variables shared by all commands in the manifest. When multiple overlays are used, their env lists are merged in order. Each entry supports environment variable expansion in the value:

env:
- FOO=bar
- FOO=prefix$FOO
- FOO=${FOO}suffix
# Result: FOO=prefixbarsuffix

The run schema applies to hooks, daemons, and jobs:

Daemons and Jobs

Daemons are background processes assumed to run continuously. The workspace automatically restarts them if they stop.

Jobs run on the cron schedule defined in the schedule field.

All commands run as the owner user. Use sudo for commands requiring root privileges.

Commands run in a headless, non-interactive shell. The default ~/.bashrc in most Debian-based distributions skips setup when not running interactively, so environment variables added to ~/.bashrc may not be available. To force interactive mode for a specific command:

cmd: exec bash -i -c COMMAND

To prevent a daemon or job from launching automatically when the workspace starts:

daemons:
  server:
    run:
      cmd: ./server
    disable_on_start: true
jobs:
  housekeeper:
    run:
      cmd: ./housekeep
    schedule: '* * * * *'
    disable_on_start: true

Managing Daemons and Jobs via CLI

cs start       # Start a daemon
cs stop        # Stop a daemon
cs restart     # Restart a daemon
cs ps          # List daemons and jobs with their status

cs sandbox job enable   # Enable a job
cs sandbox job disable  # Disable a job

Logs from daemons and jobs are stored under /var/log/sandbox and automatically rotated. They can also be viewed from the Web UI. See Log View.

Auto Mode

A workspace can be placed in Auto Mode, which automatically detects new commits from the remote repository, pulls them, and then re-runs the post-checkout and build hooks and relaunches all daemons.

This mode is useful for sandboxes running autonomous tasks where no manual changes to the working copy are expected. Auto Mode automatically discards any outstanding local changes before pulling new commits. A warning is displayed when the workspace is accessed in this state.

Toggle Auto Mode from the Web UI or using the CLI:

cs sandbox update my-sandbox -m workspace1=AUTO -m workspace2=MANUAL

Setup Scripts

A workspace runs the following setup scripts if they are present with executable permissions:

All setup scripts run after all snapshots are applied and file injections are complete, and before checkout tasks begin.

File Injection

File injection is a complementary mechanism that provides flexibility beyond snapshots. Files can be declared in the workspace definition and injected into the filesystem after snapshots are applied. This allows setup scripts themselves to be injected rather than baked into snapshots.

Example:

workspaces:
- name: example
  system:
    files:
    - path: /etc/sandbox.d/setup
      mode: '0755'
      overwrite: true
      content: |
        #!/bin/bash
        echo "Performing system-level customization"
    - path: ~/.sandbox/setup
      mode: '0700'
      template: |
        #!/bin/bash
        echo "Performing user-level customization for {{env "$SANDBOX_OWNER_EMAIL"}}"
        echo "{{secret "some-token"}}" >~/.my-token

Field reference:

Global Daemons

Global daemons are launched at the system level under root during startup, before any checkout tasks run. They can be defined in two ways:

In the workspace definition:

workspaces:
- name: example
  system:
    daemons:
    - name: foo
      run:
        cmd: foo args

In the workspace filesystem, under /etc/sandbox.d/daemons, as files with .json, .yaml, or .yml suffixes:

name: foo
run:
  cmd: foo args

Global daemons are controlled with the same CLI commands as checkout daemons: cs start/stop/restart and cs ps.

Impact of Failures

The workspace checks exit codes to determine success (zero) or failure (non-zero).

Failures in setup scripts and checkout hooks block the start sequence and put the workspace into a Problematic state. By default, the workspace retries indefinitely. Auto Retry can be disabled if manual debugging access is needed.

Stuck scripts (those that never exit) will stall the workspace in the Setting Up or Launching state indefinitely. Ensure:

Failures in daemons also flag the workspace as Problematic, unless the daemon was explicitly stopped from the Web UI or CLI.

See Also