Dev environment automation — install.sh
Setting up seven microservices by hand -- cloning repos, copying .env files, generating TLS certs, updating /etc/hosts, creating Docker networks, generating secrets -- takes about an hour and is painfully error-prone. I wrote a single shell script that does the whole thing.
The setup problem
The portfolio project runs seven services: infra (Traefik, RabbitMQ, monitoring), frontend, sso, admin, users, blog, and analytics. Each has its own docker-compose.yml, a Compose-level .env, and a Laravel-level src/.env. The domain name appears in dozens of places. Shared secrets (RabbitMQ password, DB credentials, OAuth2 client secret, internal API keys, Passport keypair) must be consistent across services. On top of that, you need TLS certificates for over a dozen subdomains and matching entries in /etc/hosts.
I had been doing this manually for weeks. Every clean setup meant repeating the same tedious steps. Time to automate.
What install.sh does
The script runs everything sequentially in a single pass. It accepts flags for customization:
./install.sh
./install.sh --domain myapp.local --repo-base git@github.com:otheruser
./install.sh --purge # tear down everything for a clean reinstall
First, it clones all seven repositories based on a services array:
SERVICES=(
"infra:infrastructure_service"
"frontend:frontend_service"
"sso:sso_service"
"admin:admin_service"
"users:users_service"
"blog:blog_service"
"analytics:analytics_service"
)
for entry in "${SERVICES[@]}"; do
dir="${entry%%:*}"
repo="${entry##*:}"
git clone "${REPO_BASE}/${repo}.git" "$dir"
done
After cloning, it copies .env.example to .env for every service, substitutes the domain via sed, sets the host UID/GID, and wires up database connections in the Laravel src/.env files.
Secret generation
Secrets are generated with openssl rand and injected into .env files using env_set_once -- a helper that only writes a value when the current one is empty. This makes the script safe to re-run without overwriting existing credentials:
env_set_once() {
local f="$1" k="$2" v="$3"
[ -f "$f" ] || return 0
local current
current=$(grep "^${k}=" "$f" | cut -d= -f2) || true
[ -n "$current" ] && return 0
sed -i "s|^${k}=.*|${k}=${v}|" "$f"
}
The script generates and distributes: a shared RabbitMQ password, MySQL passwords, an SSO OAuth2 client secret, internal API keys for Users and Analytics, and a unique APP_KEY for each Laravel service. After migrations, it also generates Passport OAuth2 keypairs inside the SSO container and writes them to both sso/src/.env and users/src/.env.
TLS certificates
mkcert generates trusted certificates for the base domain and all subdomains (frontend, sso, admin, traefik, rabbitmq, grafana, prometheus, and more). Certs land in infra/certs/. The Vite dev server certs are copied separately to frontend/src/certs/ so hot reload works over HTTPS. Traefik picks up the certs via a dynamic config file generated by envsubst from a template.
Idempotent by design
Every step checks whether it has already been completed. Existing repos are skipped, existing .env files are not overwritten, existing certs are left in place, and env_set_once preserves previously generated secrets. Running ./install.sh twice produces the same result as running it once.
The --purge flag provides the inverse: it stops all services, removes cloned directories, and deletes database volumes -- giving you a clean slate for a fresh install.
Result
One command, a few minutes of waiting, and the full local environment is up: seven services behind Traefik with valid TLS, all secrets wired, databases migrated, Passport keys generated. No manual steps, no documentation to follow.