Exec
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.
Use Cases
Section titled “Use Cases”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.
HCL Syntax
Section titled “HCL Syntax”Basic Syntax
Section titled “Basic Syntax”resource "exec" "name" {  script = <<-EOF    #!/bin/bash    echo "Hello World"  EOF}Local Execution Syntax
Section titled “Local Execution Syntax”resource "exec" "local" {  script = <<-EOF    #!/bin/bash    echo "Running locally..."    echo "RESULT=success" >> ${EXEC_OUTPUT}  EOF
  working_directory = "/tmp"  timeout = "300s"  daemon = false
  environment = {    ENV_VAR = "value"    PATH = "/usr/local/bin:/usr/bin:/bin"  }}Remote Execution in New Container Syntax
Section titled “Remote Execution in New Container Syntax”resource "exec" "new_container" {  script = <<-EOF    #!/bin/bash    echo "Running in new container..."    echo "RESULT=success" >> ${EXEC_OUTPUT}  EOF
  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 = <<-EOF    #!/bin/bash    echo "Running in existing container..."    echo "RESULT=success" >> ${EXEC_OUTPUT}  EOF
  timeout = "300s"
  environment = {    ENV_VAR = "value"  }
  target = resource.container.web}Fields
Section titled “Fields”| Field | Required | Type | Description | 
|---|---|---|---|
| script | ✓ | string | Script content to execute | 
| working_ | string | Working directory for script execution. Defaults to current directory. | |
| timeout | string | Maximum execution time. Defaults to “300s”. | |
| daemon | bool | Run as background daemon (local execution only). Defaults to false. | |
| environment | map(string) | Environment variables for script. Defaults to empty map. | |
| image | block | Create new container for execution | |
| target | reference to container | Execute in existing container | |
| network | block | Network attachments (repeatable) | |
| volume | block | Volume mounts (repeatable) | |
| run_ | block | User configuration for remote execution | 
Image Block
Section titled “Image Block”exec → image
Docker image configuration for standalone container execution.
| Field | Required | Type | Description | 
|---|---|---|---|
| name | ✓ | string | Docker image name with tag | 
| username | string | Username for private registry | |
| password | string | Password for private registry | 
Network Block
Section titled “Network Block”exec → network
Network configuration for container-based execution (repeatable).
| Field | Required | Type | Description | 
|---|---|---|---|
| id | ✓ | reference to network | Reference to network resource | 
| ip_ | string | Static IP address. Auto-assigned if not specified. | |
| aliases | list(string) | Network aliases. Defaults to empty list. | 
Volume Block
Section titled “Volume Block”exec → volume
Volume mount configuration for container-based execution (repeatable).
| Field | Required | Type | Description | 
|---|---|---|---|
| source | ✓ | string | Source path or volume name | 
| destination | ✓ | string | Mount path inside container | 
| type | string | Volume type: “bind”, “volume”, or “tmpfs”. Defaults to “bind”. | |
| read_ | bool | Mount as read-only. Defaults to false. | 
Run As Block
Section titled “Run As Block”exec → run_as
User configuration for container-based execution.
| Field | Required | Type | Description | 
|---|---|---|---|
| user | ✓ | string | Username or UID | 
| group | ✓ | string | Group name or GID | 
Computed Attributes
Section titled “Computed Attributes”These attributes are set by the system after script execution:
| Field | Type | Description | 
|---|---|---|
| exit_code | int | Exit code returned by the script | 
| output | map(any) | Key-value pairs written to EXEC_OUTPUT | 
| pid | int | Process ID (local execution only) | 
| checksum | string | Checksum of the script content | 
Output Variables
Section titled “Output Variables”Scripts can set output variables by writing to the $EXEC_OUTPUT file:
#!/bin/bash# Set output variablesecho "STATUS=completed" >> $EXEC_OUTPUTecho "COUNT=42" >> $EXEC_OUTPUTecho "MESSAGE=Hello World" >> $EXEC_OUTPUTAccess outputs in other resources:
output "status" {  value = resource.exec.deploy.output.STATUS}Validation Rules
Section titled “Validation Rules”All Execution Modes
Section titled “All Execution Modes”- Script timeout must be a valid Go duration string
- scriptfield is required for all execution modes
Local Execution Mode
Section titled “Local Execution Mode”- Cannot use image,target,network,volume, orrun_asfields
- daemonand- working_directoryare only available for local execution
Remote Execution Modes
Section titled “Remote Execution Modes”- Must specify either image(new container) ortarget(existing container), but not both
- network,- volume, and- run_asare only valid for remote execution
- Volume source paths are made absolute relative to config file location
- daemonand- working_directoryare not supported for remote execution
Examples
Section titled “Examples”Local Script Execution (Local Mode)
Section titled “Local Script Execution (Local Mode)”resource "exec" "setup" {  script = <<-EOF    #!/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  EOF
  environment = {    DEBIAN_FRONTEND = "noninteractive"  }
  timeout = "600s"}Background Daemon Process (Local Mode)
Section titled “Background Daemon Process (Local Mode)”resource "exec" "web_server" {  script = <<-EOF    #!/bin/bash    cd /var/www/html    python3 -m http.server 8080  EOF
  daemon = true  working_directory = "/var/www/html"
  environment = {    PORT = "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 = <<-EOF    #!/bin/sh    cd /app    npm install    npm run build
    echo "BUILD_STATUS=success" >> $EXEC_OUTPUT    echo "BUILD_TIME=$(date -Iseconds)" >> $EXEC_OUTPUT  EOF
  volume {    source = "./src"    destination = "/app"    type = "bind"  }
  environment = {    NODE_ENV = "production"  }}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 = <<-EOF    #!/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  EOF
  timeout = "120s"}Multi-Step Local Deployment (Local Mode)
Section titled “Multi-Step Local Deployment (Local Mode)”# Download and extract applicationresource "exec" "download_app" {  script = <<-EOF    #!/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  EOF
  timeout = "300s"}
# Configure applicationresource "exec" "configure_app" {  depends_on = [resource.exec.download_app]
  script = <<-EOF    #!/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  EOF
  environment = {    CONFIG_ENV = "production"  }}
# Start applicationresource "exec" "start_app" {  depends_on = [resource.exec.configure_app]
  script = <<-EOF    #!/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  EOF
  daemon = true}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 = <<-EOF    #!/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  EOF}Configuration File Generation (Local Mode)
Section titled “Configuration File Generation (Local Mode)”resource "exec" "generate_config" {  script = <<-EOF    #!/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  EOF
  working_directory = "/tmp"  timeout = "60s"}Script Templates
Section titled “Script Templates”Using External Script Files
Section titled “Using External Script Files”# Load script from fileresource "exec" "external_script" {  script = file("./scripts/setup.sh")
  environment = {    ENV = "production"  }}
# Use template file with variablesresource "template" "install_script" {  source = "./templates/install.sh.tpl"  destination = "./scripts/install.sh"
  variables = {    version = "1.2.0"    environment = "production"  }}
resource "exec" "install" {  depends_on = [resource.template.install_script]
  script = file(resource.template.install_script.destination)}Best Practices
Section titled “Best Practices”General Best Practices
Section titled “General Best Practices”- Error Handling: Use set -eto exit on errors and proper error checking
- Timeouts: Set appropriate timeouts for long-running operations
- Output Variables: Use EXEC_OUTPUT for passing data to other resources
- Idempotency: Write scripts that can be run multiple times safely
- Security: Avoid hardcoding secrets; use environment variables
- Logging: Include meaningful output for debugging and monitoring
- Dependencies: Use depends_onto ensure proper execution order
- Resource Cleanup: Clean up temporary resources and processes
Mode-Specific Best Practices
Section titled “Mode-Specific Best Practices”Local Execution Mode
Section titled “Local Execution Mode”- Use working_directoryfor scripts that need a specific starting location
- Leverage daemon = truefor background services that should persist
- Consider process lifecycle management for long-running processes
- Use absolute paths when working with files outside the working directory
Remote Execution in New Container Mode
Section titled “Remote Execution in New Container Mode”- 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_asfor 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
