Setup and Provision Scripts
Configure setup and provision scripts that prepare your DevBoxer sandboxes.
Setup scripts run every time a sandbox starts. Put expensive, reusable work in a provision script so DevBoxer can prepare a warm sandbox snapshot ahead of time.
Both scripts have a 30-minute timeout. If a script takes longer than 30 minutes, sandbox initialization will fail.
For expensive reusable work, use a devboxer-provision.sh script instead.
Provision scripts run on Modal before DevBoxer creates a reusable snapshot for
the environment. This is a good place for dependency installs, package cache
warming, toolchain setup, or compiling shared libraries.
The agent runs inside a Bash shell environment where the ~/.bashrc file is automatically sourced.
If you install binaries, tools, SDKs, or other dependencies make sure they are added to the PATH when the ~/.bashrc file is sourced.
echo "export PATH='$PATH:/path/to/bin'" >> ~/.bashrcProvision Scripts vs Setup Scripts
DevBoxer supports two scripts for preparing a repository:
| Script | Runs when | Best for |
|---|---|---|
devboxer-provision.sh | Ahead of time to create a reusable warm sandbox snapshot for the repository | Expensive, reusable work like installing dependencies, compiling libraries, and warming caches |
devboxer-setup.sh | Each time a task sandbox starts | Lightweight per-task work like migrations, branch-specific setup, local service setup, and checks |
Provision scripts make new tasks start faster by moving slow, reusable setup out of the task startup path. For example, if pnpm install runs during provisioning, later task setup can reuse the warmed package store and complete much more quickly.
#!/bin/bash
set -euo pipefail
pnpm install
pnpm build#!/bin/bash
set -euo pipefail
# Keep this lightweight and safe to run for every task.
pnpm install
pnpm db:migrate || trueIt is okay to keep idempotent install commands, such as pnpm install, in both scripts. The provision script warms the cache, and the setup script verifies the task sandbox is ready.
Files written by your provision script under the DevBoxer workspace and package-manager cache directories may be reused by future task sandboxes for that environment. Avoid writing long-lived secrets to disk during provisioning.
Provisioning is best for repository files and dependency caches. System-level changes, global tool installs, and shell profile changes such as edits to ~/.bashrc should usually stay in devboxer-setup.sh.
Configuration Methods
There are two ways to configure setup scripts and provision scripts:
1. Environment-Specific Script
Configured in your DevBoxer environment settings, environment-specific scripts are private to you and take precedence over repository scripts.
This is useful for user-specific customizations.
Navigate to Environments
Save to Environment2. Repository Script
Add a devboxer-provision.sh or devboxer-setup.sh file to the root of your repository.
This is version controlled with your code and shared across all users working with this repository.
This is useful for standard project setup.
#!/bin/bash
# This script runs for all users working with this repository
./scripts/setup.shAdd a devboxer-provision.sh file for reusable setup that can be snapshotted:
#!/bin/bash
# This script runs on the default branch and warms the reusable environment
pnpm install
cargo fetchYou can keep idempotent install commands in both scripts. The provision script warms the dependency store, and the setup script can do a quick verification or branch-specific install when each thread starts.
Skipping Setup Scripts
You can skip running the devboxer-setup.sh script when creating tasks and automations by toggling the Skip Repository Setup setting in the task or automation creation UI.
This is useful for quick operations that don't require the full environment setup, such as simple bug fixes or documentation updates.
Skipping setup does not skip provisioned warm snapshots. Provisioning happens separately so future task sandboxes can still benefit from reusable dependencies and caches.
Example Provision Scripts
Node.js / pnpm
#!/bin/bash
set -euo pipefail
corepack enable
pnpm install#!/bin/bash
set -euo pipefail
# Fast when the provision script has already warmed the pnpm store.
pnpm installRust
#!/bin/bash
set -euo pipefail
cargo fetch
cargo build#!/bin/bash
set -euo pipefail
cargo fetchPython
#!/bin/bash
set -euo pipefail
if [ -f pyproject.toml ]; then
uv sync
elif [ -f requirements.txt ]; then
python -m pip install -r requirements.txt
fiExample Setup Scripts
The examples below install tools or update shell startup files, so they belong in devboxer-setup.sh.
Go
#!/bin/bash
INSTALL_DIR="/usr/local"
curl -fsSL https://go.dev/dl/go1.25.0.linux-amd64.tar.gz | tar -C ${INSTALL_DIR} -xz
# Make sure that the go binary is in the PATH when bashrc is sourced
echo "export PATH=\$PATH:${INSTALL_DIR}/go/bin" >> ~/.bashrcCustom Node.js Version
#!/bin/bash
node -v
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# initialize nvm in this shell
source ~/.nvm/nvm.sh >/dev/null
# install the version we want
nvm install 20
nvm use 20
nvm alias default 20
# Add nvm to the bashrc
echo 'source ~/.nvm/nvm.sh >/dev/null 2>&1' >> ~/.bashrcRuby
#!/bin/bash
# Install RVM
gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys \
409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable
# Source RVM
source /etc/profile.d/rvm.sh
# Ruby install + default
rvm install 3.4.4
rvm --default use 3.4.4
# Persist to future shells
echo '[[ -s /etc/profile.d/rvm.sh ]] && source /etc/profile.d/rvm.sh >/dev/null 2>&1' >> ~/.bashrcJava
#!/bin/bash
JAVA_VERSION="21"
JAVA_DOWNLOAD_URL="https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.tar.gz"
echo "Downloading OpenJDK $JAVA_VERSION from Oracle..."
curl -fsSL "$JAVA_DOWNLOAD_URL" -o /tmp/openjdk.tar.gz
echo "Extracting to /usr/local/java..."
mkdir -p /usr/local/java
tar -xzf /tmp/openjdk.tar.gz -C /usr/local/java --strip-components=1
rm /tmp/openjdk.tar.gz
# Make sure that the java binary is in the PATH when bashrc is sourced
echo 'export JAVA_HOME=/usr/local/java' >> ~/.bashrc
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> ~/.bashrcSwift
#!/bin/bash
set -euo pipefail
# Desired Swift version
SWIFT_VERSION="6.1"
# Install runtime dependencies for Swift toolchains
sudo apt-get update -y
sudo apt-get install -y \
libncurses6 \
libcurl4 \
libxml2 \
libedit2 \
libsqlite3-0 \
zlib1g \
ca-certificates
# Install mise
curl -fsSL https://mise.run | sh
export PATH="$HOME/.local/bin:$PATH"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
# Ensure mise is active in THIS shell
eval "$("$HOME/.local/bin/mise" activate bash)"
# Enable experimental features for Swift
mise settings experimental=true
# Install Swift version
mise install "swift@${SWIFT_VERSION}"
# Set it as global default
mise use --global "swift@${SWIFT_VERSION}"
# Verify installation
mise x -- swift --version
# (Optional) clear caches to save space
mise cache clear || true
rm -rf "$HOME/.cache/mise" "$HOME/.local/share/mise/downloads"
# Ensure mise activates in future shells
echo 'eval "$(mise activate bash)"' >> ~/.bashrc