Skip to main content

Local environment

Audience: developers & AI agents · Scope: running and inspecting the site locally · Last reviewed: 2026-07-12

TL;DR: DDEV (Docker) is the local stack — MySQL + nginx, mirroring production (DDEV ships PHP 8.2, prod targets 8.1). ddev starthttps://betacalco.ddev.site. Reproducible and committed under .ddev/README.md. See Running with DDEV.

Database is MySQL, prefix e5xae_, credentials in docroot/configuration.php (git-ignored); reach it with ddev mysql. Migrations apply automatically on the next page load.

Don't work in the main checkout — work in a worktree. tools/worktree.sh create <slug> gives a branch its own DDEV project, database, HTTPS host and copy of the runtime data, so migrations, settings changes and test runs can't reach this install. See worktrees.md. Everything on this page describes the stack itself and applies equally to a worktree.

MAMP PRO is gone. The old https://betacalco.local:7890 vhost is no longer used or supported — everything local runs on DDEV. If you still have the MAMP install, nothing here depends on it.

Running with DDEV (Docker)

The container stack mirrors production (PHP 8.2 — DDEV 1.25 dropped 8.1 — MySQL 8.0, nginx-fpm) and is fully committed, so it's reproducible across machines. Full setup, commands, and the ionCube wrinkle are in .ddev/README.md — the short version:

ddev start # build + start; post-start hook prepares dirs & config
ddev import-db --file=path/to/dump.sql # load a dump (prefix e5xae_)
ddev launch # open https://betacalco.ddev.site

Two things stay per-machine: docroot/configuration.php and a DB dump. On a fresh checkout the post-start hook installs a DDEV-ready configuration.php from .ddev/configuration.ddev.php automatically; an existing file is left untouched, so a config carried over from the old MAMP setup must be replaced with that template (keeping its own $secret — see .ddev/README.md). The file holds the container's values directly; it does not switch on the environment.

The ionCube wrinkle (important). ConfigBox's five "brain" helpers (rules, calculation, configurator, addon, shapediver) ship only as ionCube-encoded builds under helpers/encoded/{13,14,15}/, and ionCube + Xdebug are incompatible on PHP 8.1–8.4. So the container installs the ionCube 15 loader (production parity — the encoded bundles run) and bind-mounts the plaintext sources from the sibling ../configbox-protected-files/protected_files/ to /var/protected_files. helpers/init.php checks /var/protected_files before the encoded bundles, so when the sibling is present the engine runs as plaintext (live edits + Xdebug); when it is absent it falls back to encoded/15 via ionCube. So the sibling is an optional bypass, not required. (ionCube + Xdebug coexist — you just can't step into encoded code, which is why the plaintext mount takes precedence.)

First-time setup (from zero)

Getting a fresh machine running. Pair with a teammate to obtain a database dump (and, if you don't want the generated one, a configuration.php) — those hold secrets and aren't in git.

  1. Prerequisites — install Docker and DDEV. No /etc/hosts edit is needed: DDEV manages betacalco.ddev.site and its TLS certificate itself.
  2. Get the code — clone the repo. docroot/ is the Joomla document root.
  3. ddev start — builds and starts the containers. The post-start hook prepares directories and, when docroot/configuration.php is missing, installs a DDEV-ready one from .ddev/configuration.ddev.php (that file is git-ignored — it holds DB creds + secrets). Keep $sef/$sef_rewrite as in SEF / clean URLs below.
  4. Database — load a dump with ddev import-db --file=dump.sql. The tools/ scripts pull the DB and media from upstream — e.g. tools/get_from_live.sh / tools/get_website_db_dump.sh (read the script first; they contain environment-specific credentials). The prefix is e5xae_.
  5. Verifyddev launch, or load https://betacalco.ddev.site/. The first request auto-applies any pending migrations. A 200 means you're up.

Capture anything machine-specific you had to figure out into notes/ (and promote durable steps back into this section).

Web

  • URL: https://betacalco.ddev.site. DDEV issues a locally-trusted certificate (after mkcert -install); curl -k still works if your shell doesn't trust it.
  • Stack: DDEV (Docker) — nginx-fpm, PHP 8.2, MySQL 8.0; document root docroot/. Config is committed under .ddev/.
  • Joomla: version 5.x (see docroot/libraries/src/Version.php).
# Trigger a request (also applies any pending migrations)
curl -k -s -o /dev/null -w "HTTP %{http_code}\n" -A "Mozilla/5.0" "https://betacalco.ddev.site/"

Database

  • Prefix: e5xae_ · Host: the db container (DDEV wires it up; nothing listens on the host).
  • Credentials: docroot/configuration.php ($user / $password / $db / $dbprefix). This file is git-ignored and .aiignore-d — don't commit it or paste secrets into docs.
# Read-only inspection — ddev mysql connects to the project database, no credentials needed
ddev mysql -e "SELECT \`key\`,\`value\` FROM e5xae_configbox_system_vars;"

The migration version state lives in e5xae_configbox_system_vars (the columns are literally `key` and `value`). See migrations.md.

SEF / clean URLs

SEF is enabled in docroot/configuration.php: $sef = true, $sef_rewrite = true, $sef_suffix = false — so URLs are clean (/path) with no index.php and no .html suffix. This is required for the routing in sef-links.md to produce pretty paths.

Static assets & cache busting

Custom CSS/JS lives under data/customization/assets/. When not in debug mode the framework serves the minified sibling (foo.min.css / foo.min.js) even though the view references the plain source, and appends a ?version= cache-buster, e.g. …/quote-landing-page.min.css?version=3.5.0-3.1.2.

That version is getApplicationVersion() (core ConfigBox, e.g. 3.5.0) + - + getReleaseNumber() (the customization release), assembled in ConfigboxViewHelper::getCacheBusterValue() (docroot/components/com_configbox/helpers/view.php). The core part only changes on a ConfigBox upgrade, so the customization half is the one you bump.

To bump the cache-buster for customizations, increment the version string returned by getReleaseNumber() in docroot/components/com_configbox/data/customization/system_overrides/releasenumber.php:

function getReleaseNumber() {
return '3.1.3'; // was 3.1.2
}

This changes ?version= on every customization asset, so returning visitors refetch instead of serving a stale cached copy.

Do this before any commit that touches static assets. Whenever you change a customization CSS/JS file, two steps are required:

  1. Regenerate the minified file — it's what's actually served. There's no build tool in the repo; use the installed minifier, e.g. csso input.css -o input.min.css.
  2. Bump getReleaseNumber() so browsers don't keep the old cached asset.

Skip either and the change won't reach users who already loaded the page.

Deploy & live-data sync

The tools/ directory holds shell scripts for pulling data from the live/staging servers and for deploying. Examples (read the script before running it):

  • tools/get_data_from_live.sh, tools/get_from_live.sh, tools/get_website_db_dump.sh, tools/get_sql_dump.sh — fetch DB / files from upstream.
  • tools/git-deploy scripts, tools/staging, tools/sync_to_excluded_paths — deployment helpers.

Deploy is git-push-based, not from master. Pushing the for_live branch to the production bare repo triggers tools/git-deploy scripts/post-receive, which rsyncs docroot/ (minus tools/sync_to_excluded_paths) to /var/www/betacalco.com, sets production perms, and clears APCu/opcode caches by curling the com_configbox bccaches&task=clearCaches endpoint. The default working branch is master; feature work happens on feature/* branches.

Excluded-from-AI/git paths worth knowing (see .aiignore): docroot/configuration.php and the generated media caches under data/store/private/custom_media/{aws,sheets_json}/.

Gotchas

  • Self-signed cert → every curl needs -k.
  • After editing a migration or adding updates/<version>.php, just load any page to apply it; if a migration throws, failed_update_detected is set and all further migrations are blocked until that flag is cleared (see migrations.md).