--- title: "Shell Deploy Script Patterns for Static Frontends" aliases: [deploy-script, cp-recursive, frontend-deploy, apache-reload] tags: [shell, bash, deploy, apache, nginx, static-files] sources: - "daily/2026-04-15.md" created: 2026-04-15 updated: 2026-04-15 --- # Shell Deploy Script Patterns for Static Frontends Bash deploy scripts for static frontend projects have two common failure modes: incomplete file copying and serving stale files after deploy. Both are silent — the script exits 0 but the deployment is wrong. ## Key Points - `cp frontend/*` glob does **not** recurse into subdirectories — use `cp -r frontend/. "$DEST/"` instead - `cp -r frontend/.` (with dot) copies all contents including hidden files and nested dirs; `cp -r frontend/` copies the directory itself into the dest - Always add `mkdir -p "$DEST"` before the copy as a safety net - After copying static files, reload the web server: `systemctl reload apache2` (or `nginx -s reload`) — otherwise the old cached version is served - Distinguish `setup.sh` (first-time init) from `deploy.sh` (all subsequent updates) — only `deploy.sh` runs on updates ## Details ### The cp Glob Pitfall ```bash # WRONG — skips subdirectories silently cp frontend/* /var/www/html/ # CORRECT — copies all contents recursively, including hidden files cp -r frontend/. /var/www/html/ ``` The shell glob `frontend/*` expands to all non-hidden entries in `frontend/` but `cp` without `-r` will skip any entries that are directories (it prints a warning but exits 0). Using `frontend/.` instead of `frontend/` as the source avoids creating a nested directory inside the destination. ### Apache / Nginx Reload Requirement Web servers cache configuration and sometimes static file handles. After overwriting files in the web root: ```bash # Apache systemctl reload apache2 # Nginx nginx -s reload # or systemctl reload nginx ``` `reload` is preferred over `restart` — it re-reads config and gracefully hands off connections without dropping active requests. ### Recommended deploy.sh Structure ```bash #!/bin/bash set -e FRONTEND_DIR="/var/www/html/myapp" BACKEND_DIR="/opt/myapp" # 1. Copy frontend files mkdir -p "$FRONTEND_DIR" cp -r frontend/. "$FRONTEND_DIR/" # 2. Rebuild and restart backend cd "$BACKEND_DIR" docker compose build docker compose up -d # 3. Reload web server systemctl reload apache2 echo "Deploy complete" ``` ### setup.sh vs deploy.sh Distinction - `setup.sh` — runs once at first install: creates directories, sets permissions, initializes the database, sets up systemd services - `deploy.sh` — runs on every update: copies code, rebuilds containers, reloads servers Never run `setup.sh` after initial setup — it may overwrite config files or re-initialize databases. ## Related Concepts - [[wiki/concepts/msal-vanilla-js-pkce]] — deploy pattern for social-reporting-tool where this was encountered - [[wiki/architecture/docker-compose-patterns]] — container restart patterns that work alongside deploy scripts ## Sources - [[daily/2026-04-15.md]] — Fixed broken deploy scripts in social listening project (commit `7a70283`)