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_OUTPUT
Access 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
script
field is required for all execution modes
Local Execution Mode
Section titled “Local Execution Mode”- Cannot use
image
,target
,network
,volume
, orrun_as
fields daemon
andworking_directory
are 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
, andrun_as
are only valid for remote execution- Volume source paths are made absolute relative to config file location
daemon
andworking_directory
are 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 -e
to 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_on
to 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_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
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_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