Docker Install Automation — Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Add a docker mode to bin/install.sh that automates Docker Compose deployment, and create bin/deploy-docker.sh for Docker-specific build/start/health-check logic.
Architecture: install.sh gains a third mode (docker) that reuses existing .env prompt helpers, skips Python/uv/migration steps, and delegates to the new bin/deploy-docker.sh. The deploy script handles pre-flight checks, docker compose build/up, health polling, and summary output.
Tech Stack: Bash, Docker Compose v2, existing .env helpers from install.sh
Task 1: Create bin/deploy-docker.sh — pre-flight checks
Files:
- Create:
bin/deploy-docker.sh
Step 1: Create the script with pre-flight checks
#!/bin/bash
#
# Docker Compose deployment script for server-maintanence
# Can be run standalone or called from install.sh
#
set -e
# Get the directory where this script is located (bin/)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Project root is parent of bin/
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
COMPOSE_FILE="$PROJECT_DIR/deploy/docker/docker-compose.yml"
cd "$PROJECT_DIR"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
# --- Pre-flight checks ---
info "Running pre-flight checks..."
# 1. .env must exist
if [ ! -f "$PROJECT_DIR/.env" ]; then
error ".env file not found."
echo " Run ./bin/install.sh first, or copy .env.sample to .env and configure it."
exit 1
fi
success ".env file found"
# 2. Docker daemon must be running
if ! docker info >/dev/null 2>&1; then
error "Docker is not running."
echo " Docker is required. Install it from https://docs.docker.com/get-docker/"
echo " and ensure the daemon is running."
exit 1
fi
success "Docker daemon is running"
# 3. docker compose v2 must be available
if ! docker compose version >/dev/null 2>&1; then
error "docker compose (v2) is not available."
echo " This project requires Docker Compose v2 (the 'docker compose' plugin)."
echo " The legacy standalone 'docker-compose' (v1) is not supported."
echo " See: https://docs.docker.com/compose/install/"
exit 1
fi
COMPOSE_VERSION=$(docker compose version --short 2>/dev/null)
success "Docker Compose v2 available ($COMPOSE_VERSION)"
echo ""
Step 2: Make the script executable
Run: chmod +x bin/deploy-docker.sh
Step 3: Test the pre-flight checks
Run: ./bin/deploy-docker.sh Expected: All three pre-flight checks pass (assuming Docker is running and .env exists), then script exits (no build/start logic yet).
Step 4: Commit
git add bin/deploy-docker.sh
git commit -m "feat: add deploy-docker.sh with pre-flight checks"
Task 2: Add build & start to bin/deploy-docker.sh
Files:
- Modify:
bin/deploy-docker.sh(append after pre-flight checks)
Step 1: Add build and start logic
Append after the echo "" at the end of the pre-flight section:
# --- Build & Start ---
info "Building Docker images..."
docker compose -f "$COMPOSE_FILE" build
success "Docker images built"
echo ""
info "Starting Docker Compose stack..."
docker compose -f "$COMPOSE_FILE" up -d
success "Docker Compose stack started"
echo ""
Step 2: Test build and start
Run: ./bin/deploy-docker.sh Expected: Images build, containers start, script completes. Verify with docker compose -f deploy/docker/docker-compose.yml ps.
Step 3: Commit
git add bin/deploy-docker.sh
git commit -m "feat: add docker build and start to deploy-docker.sh"
Task 3: Add health verification to bin/deploy-docker.sh
Files:
- Modify:
bin/deploy-docker.sh(append after start section)
Step 1: Add health verification logic
Append after the start section:
# --- Health Verification ---
info "Verifying stack health (timeout: 60s)..."
TIMEOUT=60
INTERVAL=5
ELAPSED=0
REDIS_OK=false
WEB_OK=false
CELERY_OK=false
while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
# Check redis
if [ "$REDIS_OK" = false ]; then
if docker compose -f "$COMPOSE_FILE" exec -T redis redis-cli ping 2>/dev/null | grep -q PONG; then
REDIS_OK=true
success "redis — healthy (PONG)"
fi
fi
# Check web (running and not restarting)
if [ "$WEB_OK" = false ]; then
WEB_STATE=$(docker compose -f "$COMPOSE_FILE" ps --format json web 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list):
data = data[0] if data else {}
print(data.get('State', ''))" 2>/dev/null || echo "")
if [ "$WEB_STATE" = "running" ]; then
WEB_OK=true
success "web — running"
fi
fi
# Check celery (running and not restarting)
if [ "$CELERY_OK" = false ]; then
CELERY_STATE=$(docker compose -f "$COMPOSE_FILE" ps --format json celery 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list):
data = data[0] if data else {}
print(data.get('State', ''))" 2>/dev/null || echo "")
if [ "$CELERY_STATE" = "running" ]; then
CELERY_OK=true
success "celery — running"
fi
fi
# All healthy?
if [ "$REDIS_OK" = true ] && [ "$WEB_OK" = true ] && [ "$CELERY_OK" = true ]; then
break
fi
sleep "$INTERVAL"
ELAPSED=$((ELAPSED + INTERVAL))
done
echo ""
# Report failures
FAILED=false
if [ "$REDIS_OK" = false ]; then
error "redis — not healthy after ${TIMEOUT}s"
FAILED=true
fi
if [ "$WEB_OK" = false ]; then
error "web — not healthy after ${TIMEOUT}s"
echo " Check logs: docker compose -f $COMPOSE_FILE logs web"
FAILED=true
fi
if [ "$CELERY_OK" = false ]; then
error "celery — not healthy after ${TIMEOUT}s"
echo " Check logs: docker compose -f $COMPOSE_FILE logs celery"
FAILED=true
fi
if [ "$FAILED" = true ]; then
error "One or more services failed health checks."
exit 1
fi
Step 2: Test health verification
Run: ./bin/deploy-docker.sh Expected: After build/start, script polls and reports pass/fail for redis, web, celery. All three show “healthy” / “running”.
Step 3: Commit
git add bin/deploy-docker.sh
git commit -m "feat: add health verification to deploy-docker.sh"
Task 4: Add summary output to bin/deploy-docker.sh
Files:
- Modify:
bin/deploy-docker.sh(append after health verification)
Step 1: Add summary output
Append at the end of the script:
# --- Summary ---
echo ""
echo "============================================"
echo -e "${GREEN} Docker Stack Running!${NC}"
echo "============================================"
echo ""
WEB_PORT=$(grep -E "^WEB_PORT=" "$PROJECT_DIR/.env" 2>/dev/null | cut -d= -f2)
WEB_PORT="${WEB_PORT:-8000}"
echo "Services:"
echo " - Web: http://localhost:${WEB_PORT}"
echo " - Redis: redis://localhost:6379 (internal)"
echo " - Celery: background worker"
echo ""
echo "Useful commands:"
echo " docker compose -f $COMPOSE_FILE logs -f # Follow logs"
echo " docker compose -f $COMPOSE_FILE ps # Service status"
echo " docker compose -f $COMPOSE_FILE down # Stop stack"
echo " docker compose -f $COMPOSE_FILE up -d --build # Rebuild & restart"
echo ""
Step 2: Test summary output
Run: ./bin/deploy-docker.sh Expected: After health checks pass, prints service URLs and useful commands.
Step 3: Commit
git add bin/deploy-docker.sh
git commit -m "feat: add summary output to deploy-docker.sh"
Task 5: Add docker mode to bin/install.sh
Files:
- Modify:
bin/install.sh
Step 1: Add dotenv_prompt_docker() function
Insert after the dotenv_prompt_setup() function (after line 228), before the main script body:
dotenv_prompt_docker() {
local env_file="$PROJECT_DIR/.env"
echo ""
echo "============================================"
echo " Docker environment setup (.env)"
echo "============================================"
echo ""
info "Configuring .env for Docker Compose deployment."
info "CELERY_BROKER_URL is managed by Docker Compose — skipping."
echo ""
# DJANGO_DEBUG
if ! dotenv_has_key "$env_file" "DJANGO_DEBUG"; then
read -p "DJANGO_DEBUG (1=on, 0=off, default: 1): " -r DEBUG_INPUT
DEBUG_INPUT="${DEBUG_INPUT:-1}"
dotenv_set_if_missing "$env_file" "DJANGO_DEBUG" "$DEBUG_INPUT"
fi
# DJANGO_SECRET_KEY
if ! dotenv_has_key "$env_file" "DJANGO_SECRET_KEY"; then
read -p "Generate a secure DJANGO_SECRET_KEY now? [Y/n]: " -n 1 -r
echo ""
if [[ -z "${REPLY:-}" || "${REPLY:-}" =~ ^[Yy]$ ]]; then
if command_exists python3; then
local key
key="$(python3 -c 'import secrets; print(secrets.token_urlsafe(50))')"
dotenv_set_if_missing "$env_file" "DJANGO_SECRET_KEY" "$key"
success "DJANGO_SECRET_KEY added to .env"
else
error "python3 not available; cannot generate DJANGO_SECRET_KEY."
local manual_key
manual_key="$(prompt_non_empty "Paste DJANGO_SECRET_KEY to store in .env: ")"
dotenv_set_if_missing "$env_file" "DJANGO_SECRET_KEY" "$manual_key"
fi
else
local manual_key
manual_key="$(prompt_non_empty "Paste DJANGO_SECRET_KEY to store in .env: ")"
dotenv_set_if_missing "$env_file" "DJANGO_SECRET_KEY" "$manual_key"
fi
fi
# DJANGO_ALLOWED_HOSTS
if ! dotenv_has_key "$env_file" "DJANGO_ALLOWED_HOSTS"; then
read -p "DJANGO_ALLOWED_HOSTS (comma-separated, default: localhost,127.0.0.1): " -r HOSTS_INPUT
HOSTS_INPUT="${HOSTS_INPUT:-localhost,127.0.0.1}"
dotenv_set_if_missing "$env_file" "DJANGO_ALLOWED_HOSTS" "$HOSTS_INPUT"
fi
# Safety: never eager in docker (Compose provides Redis)
dotenv_set_if_missing "$env_file" "CELERY_TASK_ALWAYS_EAGER" "0"
success ".env setup complete (Docker mode)"
}
Step 2: Replace the mode selection and main flow
Replace the main script body (lines 230–373) with a version that includes docker mode. The key changes are:
- Move Python/uv checks into a function so docker mode can skip them.
- Add mode selection prompt with 3 options.
- Docker mode: check Docker, run
dotenv_prompt_docker(), delegate todeploy-docker.sh. - Dev/prod modes: existing behavior unchanged.
Replace from line 230 (echo "") to end of file with:
check_python() {
info "Checking Python version..."
PYTHON_BIN=""
for candidate in python3.13 python3.12 python3.11 python3.10 python3; do
if command_exists "$candidate"; then
PYTHON_BIN="$candidate"
break
fi
done
if [ -z "$PYTHON_BIN" ]; then
error "Python 3 is not installed. Please install Python 3.10 or higher."
exit 1
fi
PYTHON_VERSION=$($PYTHON_BIN -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
PYTHON_MAJOR=$(echo "$PYTHON_VERSION" | cut -d. -f1)
PYTHON_MINOR=$(echo "$PYTHON_VERSION" | cut -d. -f2)
if [ "$PYTHON_MAJOR" -ge 3 ] && [ "$PYTHON_MINOR" -ge 10 ]; then
success "Python $PYTHON_VERSION found via $PYTHON_BIN (>= 3.10 required)"
else
error "Python 3.10+ is required, but found Python $PYTHON_VERSION ($PYTHON_BIN)"
info "Tip: If Python 3.10+ is installed under a different name, set PYTHON_BIN and re-run."
exit 1
fi
}
check_uv() {
info "Checking for uv package manager..."
if command_exists uv; then
UV_VERSION=$(uv --version 2>/dev/null | head -n1)
success "uv is already installed: $UV_VERSION"
else
warn "uv is not installed. Installing uv..."
if [[ "$OSTYPE" == "darwin"* ]] || [[ "$OSTYPE" == "linux"* ]]; then
curl -LsSf https://astral.sh/uv/install.sh | sh
if [ -f "$HOME/.cargo/env" ]; then
source "$HOME/.cargo/env"
fi
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
if command_exists uv; then
success "uv installed successfully"
else
error "Failed to install uv. Please install manually: https://docs.astral.sh/uv/"
exit 1
fi
else
error "Unsupported OS. Please install uv manually: https://docs.astral.sh/uv/"
exit 1
fi
fi
}
echo ""
echo "============================================"
echo " server-maintanence Installer"
echo "============================================"
echo ""
# Step 1: Mode selection
echo "Select installation mode:"
echo " 1) dev — local development (DEBUG=1, eager tasks)"
echo " 2) prod — bare-metal production (systemd, gunicorn)"
echo " 3) docker — Docker Compose stack (requires Docker running)"
echo ""
INSTALL_MODE=""
while true; do
read -p "Mode [1/2/3] (default: 1): " -r MODE_INPUT
MODE_INPUT="${MODE_INPUT:-1}"
case "$MODE_INPUT" in
1|d|dev) INSTALL_MODE="dev"; break ;;
2|p|prod) INSTALL_MODE="prod"; break ;;
3|docker) INSTALL_MODE="docker"; break ;;
*) warn "Please enter 1, 2, or 3." ;;
esac
done
info "Selected mode: $INSTALL_MODE"
echo ""
if [ "$INSTALL_MODE" = "docker" ]; then
# Docker mode: check Docker, setup .env, delegate to deploy-docker.sh
# Check Docker daemon
if ! docker info >/dev/null 2>&1; then
error "Docker is not running."
echo " Docker is required. Install it from https://docs.docker.com/get-docker/"
echo " and ensure the daemon is running."
exit 1
fi
success "Docker daemon is running"
# Setup .env
dotenv_ensure_file
dotenv_prompt_docker
# Delegate to deploy-docker.sh
echo ""
info "Handing off to deploy-docker.sh..."
exec "$SCRIPT_DIR/deploy-docker.sh"
else
# Dev/Prod mode: existing flow
# Step 2: Check Python version
check_python
# Step 3: Check/Install uv
check_uv
# Step 4: .env setup
dotenv_ensure_file
dotenv_prompt_setup
# Step 5: Sync dependencies
if [ "$INSTALL_MODE" = "dev" ]; then
info "Installing dependencies with uv sync (including development dependencies)..."
uv sync --all-extras --dev
else
info "Installing dependencies with uv sync (production only)..."
uv sync
fi
success "Dependencies installed"
# Step 6: Run migrations
info "Running database migrations..."
uv run python manage.py migrate
success "Migrations applied"
# Step 7: Run Django system checks
info "Running Django system checks..."
if uv run python manage.py check; then
success "All system checks passed"
else
warn "System checks reported issues (see above). You may want to address them."
fi
# Step 8: Summary and next steps
echo ""
echo "============================================"
echo -e "${GREEN} Installation Complete!${NC}"
echo "============================================"
echo ""
info "Your server-maintanence project is now set up."
echo ""
echo "Quick commands:"
echo " - Run health checks: uv run python manage.py check_health"
echo " - List checkers: uv run python manage.py check_health --list"
echo " - Start server: uv run python manage.py runserver"
echo " - Run tests: uv run pytest"
echo ""
echo "Documentation:"
echo " - Main README: README.md"
echo " - Agent conventions: agents.md"
echo ""
read -p "Would you like to run the health check suite now? [y/N] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
info "Running health checks..."
uv run python manage.py check_health
fi
echo ""
read -p "Would you like to set up automatic health checks via cron? [y/N] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
"$SCRIPT_DIR/setup_cron.sh"
fi
# Show existing pipeline definitions if any
echo ""
PIPELINE_COUNT=$(uv run python manage.py show_pipeline --json 2>/dev/null | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
if [ "$PIPELINE_COUNT" != "0" ] && [ "$PIPELINE_COUNT" != "" ]; then
info "Found $PIPELINE_COUNT configured pipeline(s):"
uv run python manage.py show_pipeline
echo ""
else
info "No pipelines configured yet. Run the setup wizard to create one:"
echo " uv run python manage.py setup_instance"
echo ""
fi
echo ""
read -p "Would you like to set up shell aliases (e.g., sm-check-health)? [y/N] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
"$SCRIPT_DIR/setup_aliases.sh"
fi
success "Setup complete!"
fi
Step 3: Verify dev/prod modes still work
Run: ./bin/install.sh and select 1 (dev). Expected: Existing dev flow works exactly as before — Python check, uv check, .env prompts, deps, migrations, health checks.
Step 4: Verify docker mode works
Run: ./bin/install.sh and select 3 (docker). Expected: Docker check, .env prompts (DEBUG, SECRET_KEY, ALLOWED_HOSTS — no CELERY_BROKER_URL), then hands off to deploy-docker.sh which builds, starts, health-checks, and prints summary.
Step 5: Commit
git add bin/install.sh
git commit -m "feat: add docker mode to install.sh"
Task 6: Update docs/Deployment.md
Files:
- Modify:
docs/Deployment.md(lines 56–71, the Docker Compose section intro)
Step 1: Add automation note
After the existing line “The fastest way to get a production stack running…” (line 58), add a tip about the automated path. Replace the “### 1.1 Clone and configure” section intro to include:
Insert after line 58 (The fastest way to get a production stack running. Includes Django (gunicorn), Celery worker, and Redis.):
> **Quick start:** Run `./bin/install.sh` and select **docker** mode to automate the steps below (`.env` setup, build, start, and health verification).
Step 2: Commit
git add docs/Deployment.md
git commit -m "docs: add install.sh docker mode note to Deployment.md"
Task 7: Update bin/README.md
Files:
- Modify:
bin/README.md
Step 1: Add deploy-docker.sh to the scripts table
Read bin/README.md and add a row for deploy-docker.sh in the scripts reference table, describing it as: “Docker Compose deployment — builds images, starts stack, verifies health. Called by install.sh (docker mode) or run standalone.”
Step 2: Commit
git add bin/README.md
git commit -m "docs: add deploy-docker.sh to bin/README.md"