Skip to content

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.

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 = <<-EOF
#!/bin/bash
echo "Hello World"
EOF
}
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"
}
}
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
}
FieldRequiredTypeDescription
scriptstringScript content to execute
working_directorystringWorking directory for script execution. Defaults to current directory.
timeoutstringMaximum execution time. Defaults to “300s”.
daemonboolRun as background daemon (local execution only). Defaults to false.
environmentmap(string)Environment variables for script. Defaults to empty map.
imageblockCreate new container for execution
targetreference to containerExecute in existing container
networkblockNetwork attachments (repeatable)
volumeblockVolume mounts (repeatable)
run_asblockUser configuration for remote execution

exec → image

Docker image configuration for standalone container execution.

FieldRequiredTypeDescription
namestringDocker image name with tag
usernamestringUsername for private registry
passwordstringPassword for private registry

exec → network

Network configuration for container-based execution (repeatable).

FieldRequiredTypeDescription
idreference to networkReference to network resource
ip_addressstringStatic IP address. Auto-assigned if not specified.
aliaseslist(string)Network aliases. Defaults to empty list.

exec → volume

Volume mount configuration for container-based execution (repeatable).

FieldRequiredTypeDescription
sourcestringSource path or volume name
destinationstringMount path inside container
typestringVolume type: “bind”, “volume”, or “tmpfs”. Defaults to “bind”.
read_onlyboolMount as read-only. Defaults to false.

exec → run_as

User configuration for container-based execution.

FieldRequiredTypeDescription
userstringUsername or UID
groupstringGroup name or GID

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

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
}
  • Script timeout must be a valid Go duration string
  • script field is required for all execution modes
  • Cannot use image, target, network, volume, or run_as fields
  • daemon and working_directory are only available for local execution
  • Must specify either image (new container) or target (existing container), but not both
  • network, volume, and run_as are only valid for remote execution
  • Volume source paths are made absolute relative to config file location
  • daemon and working_directory are not supported for remote execution
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"
}
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"
}
# Download and extract application
resource "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 application
resource "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 application
resource "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"
}
# Load script from file
resource "exec" "external_script" {
script = file("./scripts/setup.sh")
environment = {
ENV = "production"
}
}
# Use template file with variables
resource "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)
}
  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
  • 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