Skip to content
prod 352bb92
Browse

1 · Ship — code → test → deploy

Objective — get a finished local change live the same safe way every time: pick small-vs-big, work on develop, pass the database safety gate, then deploy through the selected non-production target(s) from Zaj-PROJECT.md before production and tag the release.

Shipping is dangerous in exactly two ways: a migration can drop data, and a bad merge can take the site down. This workflow removes both risks by making the path identical every time — a small change and a big feature flow through the same gate, so you never improvise on the day something breaks.

Read Zaj-PROJECT.md before you start. Use its environment matrix for the real non-production branch, deploy target, URL, SSH alias, PHP binary, DB engine/collation, and production target. If this ship changes composer.json, composer.lock, Node build tooling, DB driver/collation, deploy paths, or server runtime facts, rerun the parity checks and update Zaj-PROJECT.md plus .tool-versions in the same change.

For the command-only card (Patterns A–D, quick smoke checks), use the Code → test → ship runbook.

The default branch model is develop → selected non-production branch (often staging) → production, then mirror production into main as a backup. Zaj-PROJECT.md can name several non-production rows (staging-primary, qa, uat, client-demo, etc.); deploy through the row selected for this release.

Terminal window
NON_PROD_BRANCH="staging" # example; source branch from Zaj-PROJECT.md
DEPLOY_TARGET="staging" # example; Deployer target from Zaj-PROJECT.md
NON_PROD_URL="https://nonprod.example.com"
# Expected: values match the environment row selected for this ship
flowchart LR
D["develop<br/>(work here)"] --> S["non-production<br/>(selected test server)"]
S --> P["production<br/>(live)"]
P --> M["main<br/>(backup)"]

0. Sync the local DB before DB-dependent UI work

Section titled “0. Sync the local DB before DB-dependent UI work”

Use this pre-work only when the change depends on database-backed UI state — landing pages, settings, plans, seeded content, or any admin-panel value that local cannot reproduce from code alone.

Before touching code, size the change. A one-line fix and a multi-file feature take different paths into git — getting this right keeps your history clean and your develop branch deployable.

  1. Classify the change against the size table.

    SizeDefinitionAction
    Small1–3 commits, simple changeWork directly on develop
    Big4+ commits, multi-file featureCreate a temporary branch
    • ✅ You know whether you’re working directly on develop or on a NewFeature/... branch.

Stale or out-of-sync branches are the most common cause of a confusing deploy. Verify you’re on develop, up to date, and that staging/production carry nothing develop doesn’t.

  1. Pull develop and confirm the branches are synced.

    Terminal window
    git checkout develop && git pull origin develop
    git log --oneline "develop..$NON_PROD_BRANCH" # Expected: empty (no commits)
    git log --oneline "$NON_PROD_BRANCH..production" # Expected: empty (no commits)
    # Expected: on develop, up to date, both log commands print nothing
    • ✅ You’re on develop, up to date, and both range logs are empty.

If a server had admin-panel changes that aren’t in git yet, capture them with Server sync before you ship — otherwise this deploy can overwrite them.

Make the change, test it locally, and commit with the right type prefix so the history reads as a log of what kind of change shipped when.

  1. For a big change, branch first. Skip this for a small change on develop.

    Terminal window
    git checkout -b NewFeature/descriptive-name
    # Expected: switched to a new branch named NewFeature/descriptive-name
    • ✅ Big changes live on their own branch; small changes stay on develop.
  2. Make the change, test locally, then commit with a typed message. Test in the browser at your local site (e.g. PROJECT.test) before committing.

    Terminal window
    git add [files]
    git commit -m "🔨 🟪 T3 Add-Feature: [description]"
    # Bug fix instead: 🔧 🟦 T2 Fix-Bug: [description]
    # Config change: 🔧 🟦 T2 Setup-Config: [description]
    git push origin [branch]
    # Expected: the commit lands and pushes to its branch
    • ✅ The change works locally and is committed with a type prefix that matches it.

This is the non-negotiable gate before any deploy that might include migrations. A migration that drops a table or column can destroy customer data — so you inspect every pending migration and let Atlas lint flag the dangerous ones before they ever reach a server.

  1. List pending migrations, then dry-run them to read the SQL.

    Terminal window
    php artisan migrate:status # which migrations are pending
    php artisan migrate --pretend # the SQL each would run — no DB change
    # Expected: a clear list; if nothing is pending, the gate is already clear
    • ✅ You can see exactly what SQL would run — or confirm nothing is pending.
  2. Run Atlas lint and STOP on a destructive finding. Atlas flags dangerous operations by code; the two that block a deploy are DS102 (DROP TABLE) and DS103 (DROP COLUMN).

    Terminal window
    atlas migrate lint --env local --latest 1
    # Expected: no DS102 / DS103. STOP if either appears — back up the DB and get explicit approval first.
    • ✅ Lint is clean, or a destructive finding was handled (database backed up, explicit approval obtained, tested on staging first).
  3. Back up the database before any approved destructive op — rollback net first. A DROP, MODIFY COLUMN, or any migrate:fresh (which drops every table before re-running) must be preceded by a verified dump you can restore from.

    Terminal window
    # Herd Pro ships MariaDB → the dump tool is mariadb-dump (mysqldump was renamed; alias it if you prefer).
    mariadb-dump -u [DB_USER] -p [DB_NAME] > "backup-$(date +%Y%m%d-%H%M%S).sql" # or: mysqldump ...
    ls -lh backup-*.sql # Expected: a non-empty .sql dump exists before the migration runs
    • ✅ A verified, non-empty backup exists before the destructive migration runs — or there is no destructive op to guard.

5. Deploy develop → non-production → production

Section titled “5. Deploy develop → non-production → production”

With the gate clear, run the standard deploy sequence. Each environment gets a deploy, then a human test, before the next one — the selected non-production target catches what local missed; production is verified the moment it’s live.

  1. For a big change, merge the feature branch back into develop first. Skip for small changes already on develop.

    Terminal window
    git checkout develop && git merge NewFeature/descriptive-name
    # Expected: the feature branch is merged into develop
    • ✅ All the work is on develop before promotion begins.
  2. Push develop, then promote to the selected non-production branch and deploy.

    Terminal window
    git push origin develop
    git checkout "$NON_PROD_BRANCH" && git merge develop && git push origin "$NON_PROD_BRANCH"
    dep deploy "$DEPLOY_TARGET"
    # Expected: zero-downtime release completes on the selected non-production server
    • ✅ Non-production deployed cleanly — then 👤 test on $NON_PROD_URL and confirm the change works.
  3. Promote the tested non-production branch to production and deploy. 👤 USER step — production is human-gated. Only after the selected non-production target tested clean. Who runs this — 👤 you (production is human-gated). The agent prepares and verifies everything up to this point; a human runs the actual production push + deploy. Agents are correctly fenced from production deploys (real-user / real-money actions), the same way branch-protection and live-payment actions gate them.

    Terminal window
    git checkout production && git merge "$NON_PROD_BRANCH" && git push origin production
    dep deploy production
    # Expected: zero-downtime release completes on the production server
    • ✅ Production deployed (by 👤 you) — then 👤 test critical paths on production (login, checkout, the changed feature).

A live deploy isn’t done until it’s labelled and backed up. Tag the version, mirror production into main, and return to develop so the next change starts from a clean, deployable base.

  1. Tag the release, update main, and return to develop. Version scheme: first update after a major is vX.X.X-a; subsequent ones -b, -c; a significant change gets a new vX.X.Y.

    Terminal window
    git tag -a v[VERSION] -m "[Description]"
    git push origin v[VERSION]
    git checkout main && git merge production && git push origin main
    git checkout develop
    # Expected: tag pushed, main mirrors production, you're back on develop
    • ✅ The version is tagged, main matches production, and you’re on develop again. Update Zaj-CHANGELOG.md if the change was significant; delete the temp branch only with user confirmation.

Do not mark this step done until every box below is checked.

  • 🤖 Branches synced — on develop, up to date; develop..staging and staging..production both empty.
  • 🔀 Local DB Sync handled when needed — DB-dependent UI work used a staging → Herd sync first, or this gate was explicitly marked N/A.
  • 🤖 Committed with type — the change is committed with the right prefix (T3 Add-Feature, T2 Fix-Bug, etc.).
  • 🔀 DB gate clearedmigrate:status reviewed, Atlas lint clean (or destructive finding handled: verified DB dump taken first, explicit approval, tested on staging). No DS102/DS103 shipped unguarded; no migrate:fresh/reset/wipe against staging or production.
  • 👤 Non-production tested — the change verified on the selected non-production URL before promoting.
  • 👤 Production deployed + tested — 👤 you ran the production push + dep deploy production (production is human-gated), then verified critical paths on production after deploy.
  • 🤖 Tagged + main updated — version tag pushed, main mirrors production, back on develop.