How We Built It, Part 1: Install It in Docker
In Part 0 we covered why we build on Drupal, in Docker, from Recipes. Now let's put the whole site on your machine. By the end of this post you'll have Chattanooga.Digital running at localhost:8081.
What you need first
Exactly one thing: Docker. Everything else — PHP, the database, the web server, Drush — runs inside containers, so you never install them on your laptop. If docker run hello-world works, you're ready:
docker run hello-worldOn Windows, use Docker Desktop with the WSL2 backend. On macOS or Linux, Docker Desktop or the Docker Engine both work. (We'll never ask you to run sudo or install anything on the host — that's the whole point of the container approach.)
Step 1 — Clone the repository
git clone https://github.com/TortoiseWolfe/Chattanooga-Digital.git
cd Chattanooga-DigitalStep 2 — Configure one environment file
The repo ships an example env file. Copy it; the defaults work for local development out of the box:
cp .env.example .envThe one value worth knowing about is the port the site serves on. .env.example sets it to 8081 (rather than the bare-Compose fallback of 8080 — 8081 avoids colliding with other local Drupal projects, so copying the example file is what gives you 8081):
# .env
DRUPAL_PORT=8081Step 3 — Start the containers
make upThis boots four containers, all from Wodby's Drupal images so they match what we run in production:
- mariadb — the database (MariaDB 11.4)
- php — Drupal + PHP-FPM (the
drupal-cmsimage, with a 1 GB memory limit — the calendar needs the headroom, more on that in a later post) - nginx — the web server, proxying to PHP-FPM on port 8081
- crond — the scheduler that runs Drupal cron and our weekly backup
One quirk worth naming: the Wodby image copies its own stock theme over our customized one on boot, so make up runs a small script (scripts/start.sh) that restores our theme from git and rebuilds the CSS right after the containers come up. You don't have to do anything — just know that's why make up does a little more than a bare docker compose up.
Step 4 — Install Drupal, then apply the recipes
On a fresh database the site isn't built yet — there's no content, no structure. Two commands fix that:
make install
make buildmake install runs Drupal's installer on the empty database (the drupal_cms_installer profile). make build is where the magic happens — it applies the Recipes in order:
- the upstream Byte recipe (the SaaS-style base theme + its components), then
- our chattanooga_digital recipe (the rebrand, the real content types, the pages, the calendar, the forms), then
- a
post_install.phpcleanup script that removes the stock demo content and sets our homepage as the front page.
That recipe sequence is the heart of the whole project, and it's what the next several posts unpack one layer at a time.
Step 5 — Open it
Point your browser at:
http://localhost:8081You should see the Gig City homepage. To log in as the administrator, grab a one-time login link:
docker compose exec php drush uli --uri=http://localhost:8081The commands you'll actually use
A quick reference for day-to-day work (all defined in the project's Makefile):
make up # start the stack (restores theme + rebuilds CSS)
make down # stop, keeping your data
make destroy # stop and wipe all data
make shell # open a bash shell inside the PHP container
make logs # tail the PHP logsThere is also a make rebuild that wipes the volumes and rebuilds the whole site from the recipes in one shot — handy for proving the "reproducible from git" claim from Part 0. One caveat worth knowing before you reach for it: make rebuild starts the containers with a bare docker compose up -d rather than the make up helper, so it skips the settings.php heal step that make up runs first. On a from-scratch rebuild that can trip the boot quirk described just below, so if you hit it, prefer the explicit make destroy && make up && make install && make build sequence, which heals settings.php before installing.
If something goes wrong
- Port already in use: something else is on 8081 — change
DRUPAL_PORTin.envandmake down && make up. make installfails with a PHPParseErrorabout an unexpected token insettings.php: the Wodby image rewritessettings.phpfrom a template on every boot and, on a second boot, can run its substitution against its own previous output and corrupt the file.make upnormally heals this by deleting the stale file first, but a baredocker compose up(ormake rebuild) skips that step. Fix: delete the file from the codebase volume so Wodby recreates it cleanly, then install —docker run --rm -v <project>_drupal_codebase:/code alpine rm -f /code/web/sites/default/settings.php, thenmake install. The cleanest from-scratch path that avoids this entirely ismake destroy && make up && make install && make build.make buildfails with "Could not delete .../default.settings.php": the installer hardenssites/defaultto read-only, which blocks the composer scaffold step inmake build. Re-open it right before building:docker compose exec -u root php chmod u+w /var/www/html/web/sites/default, thenmake build.- The root URL shows a generic "Home" page, not the Gig City hero: the Byte recipe creates some content asynchronously and can win a race against the cleanup step. Re-run it once:
docker compose exec php drush php:script /var/www/html/recipes/chattanooga_digital/post_install.php && docker compose exec php drush cr. - Docker isn't running: start Docker Desktop (or the engine) and try again —
docker run hello-worldis the quick check.
What's next
You now have the site running. In Part 2 we open up the first recipe and see what a Drupal Recipe actually is — starting with how we turned the stock theme into the Gig City brand.
Beta-test this post with your AI
This series is open-source documentation, and documentation drifts. Help us keep it honest: point your AI coding assistant (Cursor, Claude Code, or similar) at this post and the repo, and if it finds something that no longer matches the code, open a ticket. First time? Read A Token Is an Identity (in this series) so your AI opens issues as you, with credit. Edit the three variables at the top of the block below, then paste it into your assistant:
# ─────── EDIT THESE FIRST ───────
GITHUB_USERNAME = "your-github-username" # your handle, so the issue is credited to YOU (see the Token Is an Identity post)
DEMO_REPO = "TortoiseWolfe/gig-city-drupal" # the repo to file against — or your own fork: GITHUB_USERNAME/gig-city-drupal
ARTICLE_URL = "https://www.chattanooga.digital/blog/2026-06/how-we-built-it-part-1-install-it-docker"
# ────────────────────────────────
Read ARTICLE_URL, then clone DEMO_REPO into my local environment and explore it.
Cross-check Part 1 (Install It in Docker) against the actual code — the Docker commands, the compose services, and the localhost:8081 boot.
If anything is inaccurate, out of date, or contradicts the code, open an issue on
DEMO_REPO (authenticated as GITHUB_USERNAME) so the maintainers can fix the tutorial.
A good issue:
- one problem per issue (not a catch-all list)
- a clear title naming the section, e.g. "Part N: no longer matches the code"
- cite the exact spot: the post section + the repo file/path (and line if you can)
- say what the post claims vs. what the code actually does
- suggest the fix if it's obvious
More insights
Want updates from Chattanooga.Digital?
Pre-join the co-op to receive new posts, workshop schedules, and member updates.