Skip to content

Script Payloads

Script payloads run a script file that lives in a configured storage backend, enabling complex deployment logic with conditionals, loops, and error handling.

A script payload is not a script body you type into a text box. Instead, it references a file by name (and optional relative path) inside a storage backend you have configured. Mantis tells the target to execute that file with the chosen executor.

Use Scripts ForUse Commands Instead
Multi-step workflowsSingle operations
Conditional logicSimple tool invocations
Error handlingAtomic commands
Loops and iterationQuick system commands
Variable manipulationOne-liners

A script payload is made up of:

FieldDescription
Storage backendThe configured storage where the script file is located.
File nameThe name of the script file to execute.
Relative pathOptional path within the storage backend, if the file lives in a subfolder.
ExecutorHow the script is run (see Executor Reference).
ArgumentsOptional arguments passed to the script.

To create a script payload:

  1. Select a configured storage backend.
  2. Reference the script file by its file name (and relative path if it is in a subfolder).
  3. Choose an executor.
  4. Optionally supply arguments.

The script file itself is authored and stored in your storage backend, not edited inline in Mantis.

The following are examples of script files you would place in your storage backend and then reference from a script payload.

#!/bin/bash
set -e # Exit on error
# Your deployment logic
echo "Starting deployment..."
cd /opt/myapp
# Pull latest code
git pull origin main
# Restart service
systemctl restart myapp
echo "Deployment complete!"
Terminal window
$ErrorActionPreference = "Stop"
Write-Host "Starting deployment..."
# Your deployment logic
Set-Location C:\Apps\MyApp
# Copy new files
Copy-Item -Path "\\server\share\release\*" -Destination "." -Recurse -Force
# Restart service
Restart-Service -Name "MyAppService"
Write-Host "Deployment complete!"
#!/usr/bin/env python3
import subprocess
import sys
def main():
print("Starting deployment...")
# Your deployment logic
result = subprocess.run(["docker", "compose", "up", "-d"], check=True)
print("Deployment complete!")
return 0
if __name__ == "__main__":
sys.exit(main())
ExecutorShebangError Handling
DefaultN/A (direct exec)Process exit code
Sh#!/bin/shset -e
Bash#!/bin/bashset -e
Pwsh#!/usr/bin/env pwsh$ErrorActionPreference = "Stop"
Zsh#!/bin/zshset -e

Zsh is invoked with -f, so no per-user startup file (~/.zshenv, ~/.zshrc, …) is sourced; anything the script needs from the environment has to come from the action’s own environment variables.

Mantis gives a script its runtime values in two ways: templates it renders before the script runs, and MANTIS_* environment variables the script reads while it runs.

The file name, arguments, working directory, script body and declared environment variable values are templates. Mantis renders every #{...} in them before the script runs:

TemplateExample valueResult
#{version}1.2.31.2.3
#{environment | ToLower}Stagingstaging
#{if Live}--live#{else}--dry-run#{/if}true--live
#{Mantis.Environment.Name}(system variable)Production

An argument of --version=#{version} becomes --version=1.2.3, and a file name of deploy-#{environment}.sh resolves to deploy-staging.sh. See Variables for the filters, conditionals and Mantis.* system variables a template can use.

Only #{...} is a template, so a shell script’s own ${HOME} or ${1:-default} reaches the shell exactly as written. Write ##{ for a literal #{ — for example a Ruby interpolation puts "##{name}", which the script receives as puts "#{name}".

A template that does not render — an unterminated #{, an unknown filter, a #{calc} that divides by zero — fails the step with a message naming the field, rather than running the script half-rendered.

Reference a sensitive variable bare, as #{DbPassword}. Mantis fills its value in when the command is delivered to the target, so it is never stored in plaintext with the queued command, and only the sensitive variables a script references are delivered to it. A sensitive variable cannot take a filter or appear in a condition: #{DbPassword | ToUpper} fails the step, because the value is not available where the filter would run.

A sensitive variable is not in the MANTIS_* set. To read one from the environment, declare an environment variable on the action whose value references it, such as DB_PASSWORD = #{DbPassword}.

Values at Run Time: MANTIS_* Environment Variables

Section titled “Values at Run Time: MANTIS_* Environment Variables”

Mantis also merges resolved non-sensitive variables into the execution environment as MANTIS_* environment variables (for example, MANTIS_VERSION, MANTIS_ENVIRONMENT). Your script reads these the normal way for its shell.

Bash:

#!/bin/bash
set -e
echo "Deploying version $MANTIS_VERSION to $MANTIS_ENVIRONMENT"
docker pull myregistry/myapp:"$MANTIS_VERSION"
docker tag myregistry/myapp:"$MANTIS_VERSION" myregistry/myapp:current

PowerShell:

Terminal window
$ErrorActionPreference = "Stop"
Write-Host "Deploying version $env:MANTIS_VERSION to $env:MANTIS_ENVIRONMENT"

Python:

import os
version = os.environ.get("MANTIS_VERSION")
print(f"Deploying version {version}")

Define environment variables on the action:

NameValueSensitive
DB_HOSTdb.example.comNo
DB_PASSWORD***Yes

These are in addition to the MANTIS_* variables Mantis injects for resolved deployment variables.

Bash:

#!/bin/bash
echo "Connecting to $DB_HOST"
psql -h "$DB_HOST" -U admin -c "SELECT 1"

PowerShell:

Terminal window
Write-Host "Connecting to $env:DB_HOST"
Invoke-Sqlcmd -ServerInstance $env:DB_HOST -Query "SELECT 1"

Python:

import os
db_host = os.environ.get("DB_HOST")
print(f"Connecting to {db_host}")
#!/bin/bash
set -e # Exit on any error
set -o pipefail # Exit on pipe failures
# Or handle errors explicitly
if ! docker pull myimage; then
echo "Failed to pull image"
exit 1
fi
# Cleanup on error
cleanup() {
echo "Cleaning up..."
rm -f /tmp/deploy-*
}
trap cleanup EXIT
Terminal window
$ErrorActionPreference = "Stop"
try {
# Deployment logic
Copy-Item -Path $source -Destination $dest
Restart-Service -Name $serviceName
}
catch {
Write-Error "Deployment failed: $_"
exit 1
}
finally {
# Cleanup
Remove-Item -Path $tempFiles -ErrorAction SilentlyContinue
}
#!/usr/bin/env python3
import subprocess
import sys
try:
subprocess.run(["docker", "compose", "up", "-d"], check=True)
except subprocess.CalledProcessError as e:
print(f"Deployment failed: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Unexpected error: {e}", file=sys.stderr)
sys.exit(1)

These scripts live in your storage backend. Runtime values are read from MANTIS_* environment variables.

#!/bin/bash
set -e
CURRENT=$(readlink /opt/app/current || echo "blue")
NEW=$([ "$CURRENT" = "blue" ] && echo "green" || echo "blue")
PORT=$([ "$NEW" = "blue" ] && echo 8080 || echo 8081)
echo "Current: $CURRENT, Deploying to: $NEW"
# Deploy to new slot
cd /opt/app/$NEW
docker compose pull
docker compose up -d
# Health check
for i in {1..30}; do
if curl -sf "http://localhost:$PORT/health"; then
echo "Health check passed"
break
fi
sleep 2
done
# Switch traffic
ln -sfn /opt/app/$NEW /opt/app/current
nginx -s reload
# Stop old
cd /opt/app/$CURRENT
docker compose down
#!/bin/bash
set -e
BACKUP_FILE="/backups/pre-migration-$(date +%Y%m%d-%H%M%S).sql"
echo "Creating backup..."
pg_dump "$DATABASE_URL" > "$BACKUP_FILE"
echo "Running migrations..."
if ! alembic upgrade head; then
echo "Migration failed, restoring backup..."
psql "$DATABASE_URL" < "$BACKUP_FILE"
exit 1
fi
echo "Migrations complete!"
#!/bin/bash
set -e
SERVICES=(web-1 web-2 web-3)
VERSION="$MANTIS_VERSION"
for service in "${SERVICES[@]}"; do
echo "Updating $service..."
# Remove from load balancer
curl -X POST "http://lb/api/remove?host=$service"
# Update service
ssh "$service" "docker pull myapp:$VERSION && docker compose up -d"
# Wait for health
until curl -sf "http://$service/health"; do
sleep 5
done
# Add back to load balancer
curl -X POST "http://lb/api/add?host=$service"
echo "$service updated"
done
echo "Rolling update complete!"
#!/bin/bash
set -e
ARTIFACT_URL="$MANTIS_ARTIFACT_URL"
DEST_DIR="/opt/app"
# Download artifact
curl -fSL "$ARTIFACT_URL" -o /tmp/release.tar.gz
# Extract
tar -xzf /tmp/release.tar.gz -C "$DEST_DIR"
# Cleanup
rm /tmp/release.tar.gz
#!/bin/bash
set -e
# Generate config from template using injected MANTIS_* values
cat > /opt/app/config.yml << EOF
server:
host: 0.0.0.0
port: ${MANTIS_PORT}
database:
host: ${DB_HOST}
name: ${MANTIS_DB_NAME}
logging:
level: ${MANTIS_LOG_LEVEL}
EOF
echo "Configuration written"
#!/bin/bash
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
log "Starting deployment"
log "Version: $MANTIS_VERSION"
log "Environment: $MANTIS_ENVIRONMENT"
# ... deployment steps ...
log "Deployment complete"
#!/bin/bash
echo "Step 1/4: Pulling images..."
docker pull myapp:"$MANTIS_VERSION"
echo "Step 2/4: Stopping services..."
docker compose down
echo "Step 3/4: Starting services..."
docker compose up -d
echo "Step 4/4: Running health checks..."
./health-check.sh
echo "Deployment successful"

Bad:

Terminal window
echo "Using password: $DB_PASSWORD"

Good:

Terminal window
echo "Connecting to database..."
# Password is in environment, not logged
#!/bin/bash
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
# Use $TEMP_DIR for temporary files
#!/bin/bash
set -e
VERSION="$MANTIS_VERSION"
# Validate version format
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Invalid version format: $VERSION"
exit 1
fi
  1. Minimize network calls - Batch operations where possible
  2. Use parallel execution - When operations are independent
  3. Cache dependencies - Avoid re-downloading unchanged files
  4. Clean up - Remove temporary files to free disk space

Cause: set -e with a failing command

Solution: Handle expected failures explicitly:

Terminal window
set -e
some_command || true # Ignore failure

Cause: Using single quotes, which prevent the shell from expanding variables.

Fix:

Terminal window
# Wrong
echo 'Version: $MANTIS_VERSION'
# Right
echo "Version: $MANTIS_VERSION"

Cause: The variable is not defined for the deployment, or its name differs in case.

Fix: Define the variable, or correct the name in #{name}.

Cause: A script file created on Windows may use CRLF (\r\n) line endings.

Fix: Convert the file to Unix (LF) line endings before storing it in your storage backend.