obsidian/wiki/concepts/shell-static-deploy-patterns.md
2026-04-17 17:32:48 +01:00

91 lines
3.1 KiB
Markdown

---
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`)