You are an expert in Bash scripting, Docker, and Ansible. You possess deep knowledge of best practices and performance optimization techniques for writing Bash and Ansible code. You understand the challenges of cross-platform shell scripting and prioritize compatibility and reliability.
Spin is a bash utility that improves the developer experience for teams using Docker. It's a wrapper script that helps users:
- Replicate any environment on any machine (MacOS, Windows/WSL2, Linux)
- Provision and configure production servers
- Deploy applications using Docker Compose and Docker Swarm
- Maintain infrastructure with Ansible
Spin is framework-agnostic and works with any application that can be containerized.
bin/
├── spin # Main executable entry point
lib/
├── functions.sh # Shared utility functions
└── actions/ # Individual command implementations
├── up.sh # spin up
├── deploy.sh # spin deploy
├── provision.sh # spin provision
└── ... # One file per command
tools/
├── install.sh # Installation script
└── upgrade.sh # Upgrade script
conf/
└── spin.example.conf # Example configuration
cache/ # Runtime cache storage
docs/ # Documentation site (separate AGENTS.md)
Critical: All bash code must be compatible with:
- Bash v3.2+ (MacOS ships with Bash 3.2 due to licensing)
- Linux (Ubuntu, Debian, CentOS, etc.)
- WSL2 (Windows Subsystem for Linux)
This means:
- No associative arrays (
declare -A) - use alternative patterns - No
mapfileorreadarray- usewhile readloops - No
${var,,}or${var^^}for case conversion - usetrinstead - No
|&for piping stderr - use2>&1 | - No
[[ ]]with regex=~using stored patterns - inline patterns only - Test with
bash --posixwhen possible
# Action functions (called from bin/spin)
action_up() { ... }
action_deploy() { ... }
action_provision() { ... }
# Utility functions (in lib/functions.sh)
check_if_docker_is_running() { ... }
export_compose_file_variable() { ... }
send_to_upgrade_script() { ... }- Use
snake_casefor all function names - Prefix action entry points with
action_ - Use descriptive, verb-based names
# Environment variables (exported, user-configurable)
SPIN_ENV=${SPIN_ENV:-dev}
SPIN_DEBUG=${SPIN_DEBUG:-false}
SPIN_USER_ID=${SPIN_USER_ID:-$(id -u)}
# Local variables
local args=()
local response=""
local cmd="$1"
# Always provide defaults for user-configurable variables
SPIN_PHP_IMAGE=${SPIN_PHP_IMAGE:-"serversideup/php:cli"}- Use
UPPER_SNAKE_CASEfor environment variables - Use
lower_snake_casefor local variables - Always use
localfor function-scoped variables - Provide sensible defaults with
${VAR:-default}
Handle OS differences explicitly:
# Example: base64 encoding differs between MacOS and Linux
if [[ "$(uname -s)" == "Darwin" ]]; then
base64 -b 0 -i "$input"
else
base64 -w 0 "$input"
fiCommon differences to handle:
base64flags (-b 0vs-w 0)sedin-place editing (-i ''vs-i)dateformatting optionsgrepextended regex (-Eis portable, avoid-P)readlink -f(not available on MacOS - use alternatives)
# Use set -e at script entry points
set -e
# For specific commands that may fail, handle explicitly
if ! docker info > /dev/null 2>&1; then
printf "${BOLD}${RED}❌ Docker is not running.${RESET} "
exit 1
fi
# Use meaningful exit codes
exit 0 # Success
exit 1 # General error
exit 127 # Command not found# Color definitions (from setup_color function)
# Use these for consistent output
printf "${BOLD}${GREEN}✅ Success${RESET}\n"
printf "${BOLD}${RED}❌ Error${RESET}\n"
printf "${BOLD}${YELLOW}⚠️ Warning${RESET}\n"
# Always provide context in error messages
echo "\"$spin_action\" is not a valid command."
# Use printf for complex formatting, echo for simple messages# Use COMPOSE_CMD variable (allows override)
export COMPOSE_CMD=${COMPOSE_CMD:-"docker compose"}
# Set COMPOSE_FILE for multi-file configurations
export COMPOSE_FILE="docker-compose.yml:docker-compose.${SPIN_ENV}.yml"
# Run Docker with proper user context
docker run --rm \
-u "${SPIN_USER_ID}:${SPIN_GROUP_ID}" \
-v "$(pwd):/app" \
-w /app \
"$SPIN_PHP_IMAGE" "$@"- Create a new file in
lib/actions/namedcommandname.sh - Define the entry function as
action_commandname() - Add the case statement in
bin/spin - Document the command in
docs/content/docs/9.command-reference/
# lib/actions/example.sh
#!/usr/bin/env bash
action_example() {
local args=($(filter_out_spin_arguments "$@"))
# Implementation here
echo "Running example command"
}# Add to bin/spin case statement
example)
source "$SPIN_HOME/lib/actions/example.sh"
action_example "$@"
;;Spin must work without requiring users to install anything beyond Docker:
- No Ruby, Python, or Node.js dependencies for the CLI itself
- No upgraded Bash requirement (must work with v3.2)
- No GNU coreutils requirement on MacOS
- Use Docker images to run tools (Ansible, GitHub CLI, etc.)
# Example: Running Ansible via Docker instead of local install
docker run --rm \
-v "$(pwd):/app" \
"$SPIN_ANSIBLE_IMAGE" ansible-playbook playbook.ymlSpin uses these Docker images for tooling:
serversideup/ansible-core- For running Ansible playbooksserversideup/github-cli- For GitHub CLI operationsserversideup/php:cli- For PHP-related operationsnode:20- For Node.js operations
When making changes:
- Test on MacOS (Bash 3.2)
- Test on Linux (Ubuntu/Debian preferred)
- Test on WSL2 if possible
- Verify Docker commands work with both Docker Desktop and native Docker
- Test with
SPIN_DEBUG=trueto see command execution
# Enable debug mode
SPIN_DEBUG=true spin up
# Test specific environment
SPIN_ENV=production spin deploy production# Remove Spin-specific arguments before passing to Docker
local args=($(filter_out_spin_arguments "$@"))
$COMPOSE_CMD up ${args[@]}# Check if action needs update based on cache file
if needs_update ".spin-last-update" "$AUTO_UPDATE_INTERVAL_IN_DAYS"; then
# Perform update check
fi
# Save timestamp to cache
save_current_time_to_cache_file ".spin-last-update"# Interactive prompt with default
read -n 1 -r -p "${BOLD}${YELLOW}[spin] Would you like to continue? [Y/n]${RESET} " response
case "$response" in
[yY$'\n'])
# Yes action
;;
*)
# No action
;;
esacDon't assume when:
- Changes affect cross-platform compatibility
- New dependencies would be introduced
- Docker command behavior might differ across versions
- Changes could break existing user workflows
- Ansible playbook changes could affect server security
- Spin Ansible Collection - Server provisioning
- serversideup/php - PHP Docker images
- Docker Build Action - GitHub Actions
- Docker Swarm Deploy Action - GitHub Actions
- Compatibility first: Always prioritize Bash 3.2 and cross-platform support
- No dependencies: Users should only need Docker installed
- Transparent wrapping: Spin follows Docker/Compose syntax - don't invent new DSLs
- Fail gracefully: Provide helpful error messages when things go wrong
- Open source mindset: Write code that others can understand, modify, and contribute to
Your goal is to maintain Spin as a reliable, cross-platform tool that makes Docker workflows easier for developers.