← Guides

Bash Scripting for AWS Deployments

BashAWSScriptingDevOpsAutomationEC2 August 2026

Bash Scripting for AWS Deployments

What is Bash?

Bash is a command-line shell and scripting language. When engineers write scripts to automate tasks — building code, deploying apps, checking server health — bash is almost always what they’re using. It’s the default shell on most Linux servers, and therefore on most AWS EC2 instances.


Script Structure

Every bash script starts with a shebang — a line that tells the OS which interpreter to use.

#!/bin/bash

echo "Hello from bash"

Making a script executable and running it

chmod +x script.sh   # make it executable (only needed once)
./script.sh          # run it

Core Bash Concepts

Variables

NAME="acebook"
VERSION="1.0.3"
ENVIRONMENT="staging"

echo "Deploying $NAME version $VERSION to $ENVIRONMENT"

Always quote variables ("$VAR") to avoid unexpected behaviour when values contain spaces.

Arguments

When you run a script, you can pass arguments to it:

./deploy.sh production

Inside the script:

$0   # the script name itself
$1   # first argument  → "production"
$2   # second argument
$@   # all arguments as a list

Command substitution

Run a command and capture its output into a variable using $():

STATE=$(aws ec2 describe-instances \
  --instance-ids "$INSTANCE_ID" \
  --query 'Reservations[0].Instances[0].State.Name' \
  --output text)

echo "Instance state is: $STATE"

Conditionals

if [ "$STATE" == "running" ]; then
  echo "Ready to deploy"
elif [ "$STATE" == "stopped" ]; then
  echo "Instance is stopped"
else
  echo "Unknown state: $STATE"
fi

Common test flags:

FlagMeaning
-z "$VAR"True if variable is empty
-n "$VAR"True if variable is not empty
-f "$FILE"True if file exists
-d "$DIR"True if directory exists
==String equality
!=String inequality
-eqNumeric equality
-neNumeric not equal

Loops

STEPS=("Pull code" "Install dependencies" "Run migrations" "Restart app")

for STEP in "${STEPS[@]}"; do
  echo "Running: $STEP"
done

Functions

log() {
  echo "[$(date '+%H:%M:%S')] $1"
}

log "Starting deployment"
log "Done"

Exit codes

Every command returns an exit code. 0 means success, anything else means failure.

exit 0   # success — CI pipeline continues
exit 1   # failure — CI pipeline stops

Check the exit code of the last command with $?:

aws s3 cp app.zip s3://my-bucket/
if [ $? -ne 0 ]; then
  echo "Upload failed"
  exit 1
fi

Or use set -e at the top of your script to automatically exit on any error:

#!/bin/bash
set -e   # exit immediately if any command fails

User input

read -p "Are you sure you want to deploy to production? (yes/no): " CONFIRM

if [ "$CONFIRM" != "yes" ]; then
  echo "Deployment cancelled."
  exit 0
fi

AWS CLI Basics

The AWS CLI lets you interact with AWS services from a bash script.

Check instance state

aws ec2 describe-instances \
  --instance-ids "$INSTANCE_ID" \
  --query 'Reservations[0].Instances[0].State.Name' \
  --output text
  • --query filters the JSON response using JMESPath — without it you get the full JSON output
  • --output text gives plain text instead of JSON, making it easy to use in scripts

Copy a file to S3

aws s3 cp app.zip s3://my-bucket/releases/app.zip

Trigger a CodeDeploy deployment

aws deploy create-deployment \
  --application-name my-app \
  --deployment-group-name production \
  --s3-location bucket=my-bucket,key=releases/app.zip,bundleType=zip

Script 1 — check_environment.sh

This script checks that an EC2 instance is running before a deployment starts.

#!/bin/bash

INSTANCE_ID=$1

# Validate argument
if [ -z "$INSTANCE_ID" ]; then
  echo "Usage: $0 <instance-id>"
  exit 1
fi

echo "Checking EC2 instance: $INSTANCE_ID"

# Get instance state from AWS CLI
STATE=$(aws ec2 describe-instances \
  --instance-ids "$INSTANCE_ID" \
  --query 'Reservations[0].Instances[0].State.Name' \
  --output text)

# Evaluate state
if [ "$STATE" == "running" ]; then
  echo "✓ Instance $INSTANCE_ID is running. Ready to deploy."
  exit 0
else
  echo "✗ Instance $INSTANCE_ID is not running. Current state: $STATE"
  exit 1
fi

Run it:

chmod +x check_environment.sh
./check_environment.sh i-0abc1234def567890

How it works step by step:

  1. $1 receives the instance ID passed as an argument
  2. -z checks if the argument is empty — if so, print usage and exit with an error
  3. $() runs the AWS CLI command and stores the result (e.g. running, stopped, terminated) in $STATE
  4. The conditional checks the state and exits with 0 (success) or 1 (failure)
  5. The exit code can be read by GitHub Actions or any CI tool — a failing check will stop the pipeline

Script 2 — deploy.sh

This script simulates deploying a new version of the application, handling staging and production differently.

#!/bin/bash

ENVIRONMENT=$1

# Validate argument
if [ -z "$ENVIRONMENT" ]; then
  echo "Usage: $0 <staging|production>"
  exit 1
fi

if [ "$ENVIRONMENT" != "staging" ] && [ "$ENVIRONMENT" != "production" ]; then
  echo "Error: environment must be 'staging' or 'production'"
  exit 1
fi

# Production safety gate — require explicit confirmation
if [ "$ENVIRONMENT" == "production" ]; then
  echo "⚠️  WARNING: You are deploying to PRODUCTION"
  read -p "Are you sure? (yes/no): " CONFIRM
  if [ "$CONFIRM" != "yes" ]; then
    echo "Deployment cancelled."
    exit 0
  fi
fi

# Deployment steps
STEPS=(
  "Pull latest code from S3"
  "Install dependencies"
  "Run database migrations"
  "Restart application"
)

echo ""
echo "Starting deployment to $ENVIRONMENT..."
echo "---"

for STEP in "${STEPS[@]}"; do
  echo "→ $STEP"
  sleep 1

  # Check if the last command failed
  if [ $? -ne 0 ]; then
    echo "✗ FAILED at step: $STEP"
    exit 1
  fi

  echo "  ✓ Done"
done

echo "---"
echo "✓ Deployment to $ENVIRONMENT complete."
exit 0

Run it:

chmod +x deploy.sh

./deploy.sh staging       # deploys to staging, no confirmation needed
./deploy.sh production    # prompts for confirmation before proceeding

How it works step by step:

  1. $1 receives staging or production as an argument
  2. The script validates the argument is one of the two expected values
  3. If deploying to production, read -p prompts the user — anything other than yes cancels safely
  4. The STEPS array defines the deployment sequence
  5. The for loop iterates through each step, printing progress
  6. $? checks the exit code of each step — if anything fails the script exits immediately with 1
  7. A final exit 0 tells the calling process (e.g. GitHub Actions) the deployment succeeded

Connecting the Two Scripts

In a real CI/CD pipeline you would chain these together — only deploy if the environment check passes:

#!/bin/bash

INSTANCE_ID=$1
ENVIRONMENT=$2

# Step 1: check environment
./check_environment.sh "$INSTANCE_ID"
if [ $? -ne 0 ]; then
  echo "Environment check failed. Aborting deployment."
  exit 1
fi

# Step 2: deploy
./deploy.sh "$ENVIRONMENT"

Or in a GitHub Actions workflow:

- name: Check environment
  run: ./scripts/check_environment.sh ${{ secrets.INSTANCE_ID }}

- name: Deploy
  run: ./scripts/deploy.sh production

If check_environment.sh exits with 1, GitHub Actions stops and the deploy step never runs.


Tips and Gotchas

  • Always quote variables"$VAR" not $VAR. Unquoted variables break when they contain spaces.
  • Use set -e at the top of scripts that should fail fast on any error.
  • Never hardcode credentials — use IAM roles on EC2 or GitHub Secrets in Actions. The AWS CLI picks up credentials automatically from the instance role.
  • Test with echo first — before a script actually runs AWS commands or restarts services, comment out the real commands and echo what would happen instead.
  • Exit codes matter — CI/CD tools read them. A script that always exits 0 even on failure will silently pass checks.
  • --dry-run flag — many AWS CLI commands support --dry-run to validate the command without executing it. Useful for testing.

Further Reading