Skip to content

You are viewing documentation for Instruqt 2.0 Labs - our upcoming product releasing in September 2026. For current Tracks documentation, please visitdocs.instruqt.com.

Exec


Defined insandbox.hcl

The exec resource executes scripts and commands either locally on the host machine or remotely inside containers. It supports both standalone and targeted container execution with comprehensive configuration options for environment, networking, and output handling.

As a lab author, you can use exec resources to:

  • Environment Setup: Install dependencies, configure systems, and prepare development environments for hands-on activities
  • System Configuration: Configure services, update system settings, and customize environments for specific lab scenarios
  • Custom Automation: Implement custom setup logic, cleanup operations, and specialized workflows unique to your lab content

Exec resources provide flexible script execution capabilities for both local host operations and containerized environments.

resource "exec" "name" {
script = "scripts/exec/name/script.sh"
}
resource "exec" "local" {
script = "scripts/exec/local/script.sh"
working_directory = "/tmp"
timeout = "300s"
daemon = false
environment = {
ENV_VAR = "value"
PATH = "/usr/local/bin:/usr/bin:/bin"
}
}
resource "exec" "new_container" {
script = "scripts/exec/new_container/script.sh"
timeout = "300s"
environment = {
ENV_VAR = "value"
}
image {
name = "alpine:latest"
username = "registry_user"
password = "registry_pass"
}
network {
id = resource.network.main
ip_address = "10.0.0.100"
aliases = ["exec-container"]
}
volume {
source = "./scripts"
destination = "/scripts"
type = "bind"
read_only = true
}
run_as {
user = "1000"
group = "1000"
}
}

Remote Execution in Existing Container Syntax

Section titled “Remote Execution in Existing Container Syntax”
resource "exec" "existing_container" {
script = "scripts/exec/existing_container/script.sh"
timeout = "300s"
environment = {
ENV_VAR = "value"
}
target = resource.container.web
}

Fields

FieldTypeRequiredDescription
scriptstringRelative or absolute path to a script file. Inline script content is not allowed.
working_directorystringThe working directory where the script will be executed. Defaults to root directory.
daemonboolEnable to run the script as a background daemon process
Defaultfalse
timeoutstringMaximum time to allow script execution. Format: duration string like '30s', '5m', '1h'
Default300s
ConstraintsTimeout must be a valid duration (e.g., 30s, 5m, 1h)
environmentmap(string)Environment variables that will be available to the script during execution
imageblockSpecify a Docker image to create a temporary container for script execution
targetreferencecontainerReference to an existing container resource where the script will be executed
networkblockNetworks to connect the temporary container to (only used with image)repeatable
volumeblockVolumes to mount in the container (only used with image or target)repeatable
run_asblockConfigure the user and group to execute the script as inside the container

Image

execImage

Create temporary container for execution

FieldTypeRequiredDescription
namestringDocker image name with optional tag
ConstraintsMust match `^[a-z0-9]([a-z0-9-]*[a-z0-9])*(\.[a-z0-9]([a-z0-9-]*[a-z0-9])*)*(/[a-z0-9]+([a-z0-9._-]*[a-z0-9])*)*(:[a-z0-9]+([a-z0-9._-]*[a-z0-9])*)?$`
usernamestringDocker registry user to use for private repositories
passwordstringDocker registry password to use for private repositories

Network

execNetwork

Networks to attach container to

FieldTypeRequiredDescription
idstringID of the network to attach the container
ip_addressstringStatic IP address to assign container for the network, the ip address must be within range defined by the network subnet. If this parameter is omitted an IP address will be automatically assigned.
aliaseslist(string)Aliases allow alternate names to specified for the container

Volume

execVolume

Volumes to mount

FieldTypeRequiredDescription
sourcestringThe source volume to mount in the container, can be specified as a relative `./` or absolute path `/usr/local/bin`. Relative paths are relative to the file declaring the container.
destinationstringThe destination in the container to mount the volume to, must be an absolute path
typestringThe type of the mount, can be one of the following values: - bind: bind the source path to the destination path in the container - volume: source is a Docker volume - tmpfs: create a temporary filesystem
Allowedbind volume tmpfs
Defaultbind
read_onlyboolWhether or not the volume is read only
bind_propagationstringConfigures bind propagation for Docker volume mounts, only applies to bind mounts, can be one of the following values: - shared - slave - private - rslave - rprivate For more information please see the Docker documentation.
Allowedshared private slave rslave rprivate
Defaultshared
bind_propagation_non_recursiveboolConfigures recursiveness of the bind mount. By default Docker mounts with the equivalent of `mount --rbind` meaning that mounts below the the source directory are visible in the container. For instance running `docker run --rm --mount type=bind,src=/,target=/host,readonly` busybox will make `/run` of the host available as `/host/run` in the container. To make matters even worse it will be writable (since only the toplevel bind is set readonly, not the children). If `bind_propagation_non_recursive` is set to true then the container will only see an empty `/host/run`, meaning the `tmpfs` which is typically mounted to `/run` on the host is not propagated into the container.
selinux_relabelstringConfigures Selinux relabeling for the container (usually specified as :z or :Z) and can be one of the following values: - shared (Equivalent to :z) - private (Equivalent to :Z)
Allowed shared private

Run As

execRun As

User to run the script as

FieldTypeRequiredDescription
userstringThe user to run the container as
groupstringThe group to run the container as

Computed Attributes

These attributes are set by the system and are read-only.

AttributeTypeDescription
image.idstringUnique identifier for the image, this is independent of tag and changes each time the image is built. An image that has been tagged multiple times also shares the same ID.
network[].namestringName will equal the name of the network as created by jumppad
network[].assigned_addressstringAssignedAddress will equal if IPAddress is set, else it will be the value automatically assigned from the network
pidnumberProcess ID of the executed script
exit_codenumberExit code of the completed script
outputstringScript output (stdout)
checksumstringChecksum of the script content

Scripts can set output variables by writing to the $EXEC_OUTPUT file:

#!/bin/bash
# Set output variables
echo "STATUS=completed" >> $EXEC_OUTPUT
echo "COUNT=42" >> $EXEC_OUTPUT
echo "MESSAGE=Hello World" >> $EXEC_OUTPUT

Access outputs in other resources:

output "status" {
value = resource.exec.deploy.output.STATUS
}
resource "exec" "setup" {
script = "scripts/exec/setup/script.sh"
environment = {
DEBIAN_FRONTEND = "noninteractive"
}
timeout = "600s"
}

scripts/exec/setup/script.sh:

#!/bin/bash
echo "Setting up environment..."
# Install dependencies
sudo apt-get update
sudo apt-get install -y curl wget
# Set output variables
echo "SETUP_COMPLETE=true" >> $EXEC_OUTPUT
echo "VERSION=1.0.0" >> $EXEC_OUTPUT
resource "exec" "web_server" {
script = "scripts/exec/web_server/script.sh"
daemon = true
working_directory = "/var/www/html"
environment = {
PORT = "8080"
}
}

scripts/exec/web_server/script.sh:

#!/bin/bash
cd /var/www/html
python3 -m http.server 8080

Script in New Container (Remote Mode - New Container)

Section titled “Script in New Container (Remote Mode - New Container)”
resource "exec" "build" {
image {
name = "node:18-alpine"
}
script = "scripts/exec/build/script.sh"
volume {
source = "./src"
destination = "/app"
type = "bind"
}
environment = {
NODE_ENV = "production"
}
}

scripts/exec/build/script.sh:

#!/bin/sh
cd /app
npm install
npm run build
echo "BUILD_STATUS=success" >> $EXEC_OUTPUT
echo "BUILD_TIME=$(date -Iseconds)" >> $EXEC_OUTPUT

Script in Existing Container (Remote Mode - Existing Container)

Section titled “Script in Existing Container (Remote Mode - Existing Container)”
resource "container" "database" {
image {
name = "postgres:15"
}
environment = {
POSTGRES_PASSWORD = "password"
POSTGRES_DB = "myapp"
}
port {
local = 5432
host = 5432
}
}
resource "exec" "init_database" {
target = resource.container.database
script = "scripts/exec/init_database/script.sh"
timeout = "120s"
}

scripts/exec/init_database/script.sh:

#!/bin/bash
# Wait for database to be ready
until pg_isready -U postgres; do
echo "Waiting for database..."
sleep 2
done
# Create tables
psql -U postgres -d myapp -c "
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
);
"
echo "DB_INITIALIZED=true" >> $EXEC_OUTPUT
# Download and extract application
resource "exec" "download_app" {
script = "scripts/exec/download_app/script.sh"
timeout = "300s"
}
# Configure application
resource "exec" "configure_app" {
depends_on = [resource.exec.download_app]
script = "scripts/exec/configure_app/script.sh"
environment = {
CONFIG_ENV = "production"
}
}
# Start application
resource "exec" "start_app" {
depends_on = [resource.exec.configure_app]
script = "scripts/exec/start_app/script.sh"
daemon = true
}

scripts/exec/download_app/script.sh:

#!/bin/bash
mkdir -p /tmp/app
cd /tmp/app
wget https://github.com/mycompany/app/releases/download/v1.2.0/app-v1.2.0.tar.gz
tar -xzf app-v1.2.0.tar.gz
echo "DOWNLOAD_PATH=/tmp/app/app-v1.2.0" >> $EXEC_OUTPUT

scripts/exec/configure_app/script.sh:

#!/bin/bash
APP_PATH="${resource.exec.download_app.output.DOWNLOAD_PATH}"
# Copy configuration
cp ./config/app.yml "$APP_PATH/config/"
# Set permissions
chmod +x "$APP_PATH/bin/app"
echo "CONFIG_COMPLETE=true" >> $EXEC_OUTPUT
echo "APP_READY=true" >> $EXEC_OUTPUT

scripts/exec/start_app/script.sh:

#!/bin/bash
APP_PATH="${resource.exec.download_app.output.DOWNLOAD_PATH}"
cd "$APP_PATH"
nohup ./bin/app > app.log 2>&1 &
APP_PID=$!
echo "APP_PID=$APP_PID" >> $EXEC_OUTPUT
echo "APP_STARTED=true" >> $EXEC_OUTPUT

Container Network Setup (Remote Mode - New Container)

Section titled “Container Network Setup (Remote Mode - New Container)”
resource "network" "app" {
subnet = "10.0.1.0/24"
}
resource "exec" "network_setup" {
image {
name = "alpine:latest"
}
network {
id = resource.network.app
ip_address = "10.0.1.10"
aliases = ["setup-container"]
}
script = "scripts/exec/network_setup/script.sh"
}

scripts/exec/network_setup/script.sh:

#!/bin/sh
# Install network tools
apk add --no-cache curl netcat-openbsd
# Test network connectivity
ping -c 3 google.com
# Set network status
echo "NETWORK_READY=true" >> $EXEC_OUTPUT
echo "IP_ADDRESS=$(hostname -i)" >> $EXEC_OUTPUT

Configuration File Generation (Local Mode)

Section titled “Configuration File Generation (Local Mode)”
resource "exec" "generate_config" {
script = "scripts/exec/generate_config/script.sh"
working_directory = "/tmp"
timeout = "60s"
}

scripts/exec/generate_config/script.sh:

#!/bin/bash
set -e
echo "Generating application configuration..."
# Create config directory
mkdir -p /etc/myapp
# Generate configuration file
cat > /etc/myapp/config.yml <<CONFIG
server:
host: 0.0.0.0
port: 8080
database:
host: localhost
port: 5432
name: myapp_db
logging:
level: info
file: /var/log/myapp.log
CONFIG
# Set proper permissions
chmod 644 /etc/myapp/config.yml
echo "CONFIG_PATH=/etc/myapp/config.yml" >> $EXEC_OUTPUT
echo "CONFIG_GENERATED=true" >> $EXEC_OUTPUT

Script files are paths relative to the HCL config file. You can pair the exec resource with a template resource to generate a script file before execution:

resource "template" "install_script" {
source = "./templates/install.sh.tpl"
destination = "./scripts/exec/install/script.sh"
variables = {
version = "1.2.0"
environment = "production"
}
}
resource "exec" "install" {
depends_on = [resource.template.install_script]
script = resource.template.install_script.destination
}
  1. Error Handling: Use set -e to exit on errors and proper error checking
  2. Timeouts: Set appropriate timeouts for long-running operations
  3. Output Variables: Use EXEC_OUTPUT for passing data to other resources
  4. Idempotency: Write scripts that can be run multiple times safely
  5. Security: Avoid hardcoding secrets; use environment variables
  6. Logging: Include meaningful output for debugging and monitoring
  7. Dependencies: Use depends_on to ensure proper execution order
  8. Resource Cleanup: Clean up temporary resources and processes
  9. Script Organization: Separate scripts into subdirectories — one per exec resource — for easier readability. Use scripts/exec/<id>/script.sh where <id> matches the resource name.
  • Use working_directory for scripts that need a specific starting location
  • Leverage daemon = true for background services that should persist
  • Consider process lifecycle management for long-running processes
  • Use absolute paths when working with files outside the working directory
  • Choose appropriate base images that include required tools and dependencies
  • Use volume mounts to persist data and share files with the host
  • Configure network attachments for multi-container communication
  • Set proper user permissions with run_as for security and file access
  • Keep containers lightweight by installing only necessary packages

Remote Execution in Existing Container Mode

Section titled “Remote Execution in Existing Container Mode”
  • Ensure target containers are running and healthy before execution
  • Verify required tools and dependencies are available in the target container
  • Consider container resource limits when running intensive operations
  • Use this mode for operations that need to interact with container-specific state