Skip to content

[3.0][Testing] Add a Docker development environment for MySQL and PostgreSQL - #9317

Merged
jdarwood007 merged 6 commits into
SimpleMachines:release-3.0from
albertlast:docker-dev-env
Aug 24, 2026
Merged

[3.0][Testing] Add a Docker development environment for MySQL and PostgreSQL#9317
jdarwood007 merged 6 commits into
SimpleMachines:release-3.0from
albertlast:docker-dev-env

Conversation

@albertlast

@albertlast albertlast commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Note

This change was produced by an LLM. The code, the commit messages and this
description were all written by Claude (Anthropic), driven by @albertlast. It has
not yet had human code review.

Everything stated below was verified by actually running it — a full install from
an empty database on each engine — rather than only reasoned about. Even so, please
review it as untrusted work: it may be right about what it does while not being
what SMF would prefer stylistically or architecturally.

Description

This one is a proposal rather than a fix — please close it if a bundled dev
environment is not wanted in the repository.
It adds tooling only and changes no
forum code. It is the environment the other PRs in this batch were found and verified
in, so it is offered mainly so that work is reproducible.

docker compose up -d --build gives a working 3.0 checkout with no local PHP,
Composer, MySQL or PostgreSQL install:

Service URL / address Notes
Forum http://localhost:8080 PHP 8.4 on Apache
Mailpit http://localhost:8025 catches all mail(), nothing can be sent
Adminer http://localhost:8081 database browser
MySQL localhost:3307 3307 so it cannot collide with a local server
PostgreSQL localhost:5433 5433, same reason

Both engines SMF supports are in the stack, and MySQL is the default. Both
services always start, on separate volumes, so a forum can be installed on each and
switched between. SMF_DB_TYPE decides which one the generated Settings.php points
at:

SMF_DB_TYPE=postgresql docker compose up -d

Once the forum is installed Settings.php is what counts, and the variable no longer
has any effect. The entrypoint detects that case and says so in the log rather than
appearing to ignore the setting.

Built to match other/requirements.md: the required extensions (mbstring,
fileinfo, and both mysqli and pgsql, since SMF checks for pg_connect) and the
recommended ones (gd, intl, curl, exif, ftp, xsl, zip). The engine
settings SMF asks for are pinned at server level rather than left to whatever the
images happen to ship:

  • PostgreSQL: standard_conforming_strings = on, as requirements.md requires.
  • MySQL: utf8mb4 and InnoDB, matching SMF's own table DDL. The collation is
    deliberately left at the charset default — SMF writes CHARSET=utf8mb4 with no
    COLLATE, so its tables come out utf8mb4_0900_ai_ci on MySQL 8.4, and forcing a
    different server collation would only apply to objects SMF did not create and make
    the two diverge.

The entrypoint runs composer install, waits for whichever database was selected,
generates a Settings.php pointed at it and puts install.php in place, so a fresh
checkout is ready to install on first boot.

The PostgreSQL service is named postgres rather than db, and keeps db as a
network alias so a Settings.php written by an earlier revision of this branch still
resolves.

It does not disturb the existing CI checks

Everything lives under .docker/ on purpose. check-smf-index.php requires an
index.php in every directory but skips dot directories (that is how .github
passes today), and check-smf-license.php only inspects .php files — .docker/
contains none. Re-confirmed after adding the MySQL side: all four integrity scripts
in the build job exit 0 with this branch checked out.

The only tracked file outside .docker/ and compose.yaml is .gitignore, to add
/install.php (next to the existing /upgrade.php), /.env and the compose override
filenames.

Notes for review

  • Credentials are smf / smf / smf on both engines and are deliberately
    committed — this is a throwaway local environment, not something with secrets worth
    protecting.
  • compose.yaml works with no .env file; .docker/env.example is there for
    overriding ports, versions, credentials and the engine.
  • One wrinkle documented in .docker/README.md: the installer does not take its form
    defaults from Settings.php, it uses the hardcoded ones in the database API class,
    so the database step still has to be filled in by hand. The README lists the exact
    values for both engines.
  • php.ini, the vhost and the entrypoint are copied into the image rather than
    bind-mounted, so editing them needs docker compose up -d --build web, not a
    restart. An earlier revision of the README said restart, which was wrong.
  • Worth knowing when working in a git checkout: the UpdateUnicode scheduled task
    regenerates Sources/Unicode/*.php in place via file_put_contents(), and its
    output differs from what is committed by a handful of blank lines, so the working
    tree goes dirty on its own. Not caused by this PR, but it will be noticed by anyone
    using it.

How this was verified

PostgreSQL side: full teardown and rebuild (docker compose down -v, delete
Settings.php, up again) followed by a complete install from an empty database,
repeated several times while finding the other issues in this batch.

MySQL side: same, driven through every installer step against an empty database.

  • Steps 1 through 5 (welcome, writable check, database settings, forum settings,
    database population) all return HTTP 200.
  • 72 tables created, all InnoDB, all utf8mb4.
  • Admin account and finalize complete, install.php removed, and index.php then
    serves the new forum at HTTP 200.
  • Afterwards both volumes still hold their own independent forum, confirming the two
    engines do not interfere.

docker compose config validates, and all four build job integrity scripts pass.

One thing found while verifying, which this PR depends on

The installer cannot finish on this branch without #9316. Step 6, the admin
account step, dies with:

Uncaught Error: Typed static property SMF\User::$me must not be accessed before
initialization in /var/www/html/Sources/Time.php:191

which is exactly what #9316 fixes. Merging that branch in locally was what allowed the
MySQL install above to complete, and it was then dropped again so this PR stays
tooling-only. The code involved is in Sources/Maintenance/Tools/Install.php and is
not engine-specific, so it should affect a PostgreSQL install equally; it was only
observed here on MySQL.

This branch has also been brought up to date with release-3.0, which it was ten
commits behind.

Issues References (Fixes|Related|Closes)

  1. Related: [3.0] Load the current user before finalize() records dates #9316 (the installer cannot complete without it)

Provides a reproducible local stack so contributors can work on SMF
without installing PHP, Composer or PostgreSQL on the host:

- PHP 8.4 on Apache, with every extension other/requirements.md lists as
  required (mbstring, fileinfo, pgsql, mysqli) or recommended (gd, intl,
  curl, exif, ftp, xsl, zip).
- PostgreSQL 17, with standard_conforming_strings forced on at database
  level as SMF requires.
- Mailpit, so mail() is captured locally and nothing can be sent out.
- Adminer, for browsing the database.

The entrypoint runs composer install, waits for the database, generates a
Settings.php pointed at the db service and drops install.php into place,
so a fresh checkout is ready to install on first boot.

Everything lives under .docker/ because check-smf-index.php and
check-smf-license.php skip dot directories, so the environment cannot
break the file integrity checks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
albertlast and others added 2 commits July 29, 2026 23:50
SMF supports MySQL and PostgreSQL, and until now this environment only
offered one of them. Both database services now start, and SMF_DB_TYPE
decides which one the generated Settings.php points at. It defaults to
mysql, since that is what the great majority of installs run on.

The two engines keep separate volumes, so a forum can be installed on
each and switched between by deleting Settings.php and restarting.
Settings.php wins over SMF_DB_TYPE once it exists, and the entrypoint
says so rather than silently ignoring the variable.

The postgres service is renamed from `db` to say what it is, and keeps
`db` as a network alias so Settings.php files written by the previous
version still resolve.

Engine settings are pinned the same way the postgres side already pinned
standard_conforming_strings: utf8mb4 and InnoDB, matching SMF's own table
DDL. The collation is deliberately left at the charset default, because
SMF sets CHARSET without COLLATE, and forcing one here would diverge from
the tables it creates.

Also corrects the everyday-use notes: php.ini, the vhost and the
entrypoint are copied into the image, so editing them needs a rebuild
rather than a restart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@albertlast albertlast changed the title [3.0] Add a Docker development environment using PostgreSQL [3.0] Add a Docker development environment for MySQL and PostgreSQL Jul 29, 2026
PostgreSQL logs every statement that errors together with the SQL that
caused it, with no configuration needed, and the log is only on the
container stderr. That makes `docker compose logs postgres` the most
useful debugging tool in the stack, and nothing said so.

MySQL logs server errors only, never the client statement that failed,
so the note points out the asymmetry: now that mysql is the default
engine, a suspected SQL problem is worth reproducing on postgres.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jdarwood007 jdarwood007 added this to the 3.0 Alpha 5 milestone Aug 1, 2026
@albertlast albertlast changed the title [3.0] Add a Docker development environment for MySQL and PostgreSQL [3.0][Testing] Add a Docker development environment for MySQL and PostgreSQL Aug 2, 2026
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Comment thread .docker/php/entrypoint.sh Outdated
-e "s|^\$db_user = 'root';|\$db_user = '${SMF_DB_USER}';|" \
-e "s|^\$db_passwd = '';|\$db_passwd = '${SMF_DB_PASSWD}';|" \
-e "s|^\$boardurl = 'http://127.0.0.1/smf';|\$boardurl = '${SMF_BOARDURL}';|" \
-e "s|^\$mbname = 'My Community';|\$mbname = 'SMF Dev';|" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we set the forum name here?

I am questioning:

  1. Why set it at all
  2. Why not set it to something like "SMF Dev - {GIT Revision}" and then update it on refresh, using the else statement?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, and digging into it the answer to (1) is that we should not set it at all. I will drop the line.

It never reaches the finished forum. The installer's forum-name field does not read Settings.php — its value is a constant:

// Themes/default/InstallTemplate.php:314
value="', Lang::getTxt('install_settings_name_default', file: 'Maintenance'), '"

which is 'My Community', and Install.php:668 writes $_POST['mbname'] back unconditionally. So anyone installing through the browser ends up with My Community whatever the entrypoint wrote, and before the install finishes Settings.php just redirects to install.php, so the name is never on screen either. It is write-only. (Whether the installer should honour a prepared $mbname is a separate question, so I have raised it separately as #9546 rather than smuggle a behaviour change in here.)

It is redundant from the other direction too: in #9344 install-forum.sh passes --mbname="$SMF_MBNAME", defaulting to SMF Dev in lib.sh, which is what actually names the forum you get. So the sed line is dead in both worlds.

On (2) — I like the goal, but I do not think $mbname is the right carrier, for four reasons:

  • It would fight the developer. $mbname is editable in Admin → Server Settings. Rewriting it from the else branch on every docker compose up silently reverts that, and the header of this file promises the opposite: "Everything here is idempotent: it is safe to restart the container at any time."
  • It would go stale, which is worse than absent. The checkout is bind-mounted live, so you can git switch on the host without restarting the container. The name would then name a commit you are not running. A restart-time snapshot of a live tree is a claim that quietly turns false.
  • It leaks well past the page header. $mbname is the outgoing-mail identity: digest subjects (SendDigests.php:225), database-error alert mail (ErrorHandlerService.php:510), warning PMs (IssueWarning.php:309), the export author field (ExportProfileData.php:1229). A revision string through all of that makes Mailpit output churn between runs, so you cannot diff it.
  • Portability. git is in the image and the entrypoint runs as root, so it does work here — git describe --tags --always --dirty gives v3.0-alpha.4-1217-g0ae132e78. But as www-data the same call fails with detected dubious ownership in repository at '/var/www/html', and on a Linux host, where the bind mount carries the developer's own uid rather than root's, that will bite too. Workable with safe.directory, but it is another moving part.

What I would suggest instead is to serve the same purpose where it cannot lie or leak — the entrypoint already logs a few lines at startup, and one more is accurate at the moment it prints, touches no state and reaches no mail:

log "checkout is at $(git -c safe.directory='*' -C "$BOARD_DIR" describe --tags --always --dirty 2>/dev/null || echo unknown)"

Happy to add that here if you think it earns its place, or leave it out and keep this PR to the environment itself. And if you do want the forum to announce it is a dev instance, SMF_MBNAME from #9344 is the honest way to do it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, let's drop setting it here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped in ed38bc2 — the entrypoint no longer touches $mbname, so the generated Settings.php keeps the stock My Community, which is what the installer writes back anyway.

Nothing else referenced it: the README never mentioned a forum name, and the generated Settings.php is otherwise unchanged (verified by running the same sed block against other/Settings.php — every database value and $boardurl still substitute, and the result passes php -l).

The generated Settings.php had $mbname rewritten to 'SMF Dev', which never
reached the finished forum. The installer's forum-name field does not read
Settings.php -- it defaults to the 'install_settings_name_default' string and
writes $_POST['mbname'] back unconditionally -- so a browser install always
ends up with 'My Community' whatever was written here, and before the install
finishes Settings.php only redirects to install.php, so the name is never on
screen either. The line was write-only.

Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
@jdarwood007
jdarwood007 merged commit 3aa674e into SimpleMachines:release-3.0 Aug 24, 2026
5 checks passed
@jdarwood007 jdarwood007 added the Meta Repository tools label Aug 24, 2026
albertlast added a commit to albertlast/SMF2.1 that referenced this pull request Aug 24, 2026
The Docker environment this branch was built on has landed upstream in
SimpleMachines#9317, so the copies of compose.yaml and most of .docker/ that were
riding along in this diff are now the same files release-3.0 already
has. Merging drops them from the diff and leaves the install tooling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
albertlast added a commit to albertlast/SMF2.1 that referenced this pull request Aug 24, 2026
The Docker environment this branch was built on has landed upstream in
SimpleMachines#9317, so the copies of compose.yaml and most of .docker/ that were
riding along in this diff are now the same files release-3.0 already
has. Merging drops them from the diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
albertlast added a commit to albertlast/SMF2.1 that referenced this pull request Aug 24, 2026
SimpleMachines#9317 put the Docker environment upstream, so the copies of compose.yaml
and most of .docker/ that this branch was carrying are now identical to
what release-3.0 has. Merging drops them from the diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
albertlast added a commit to albertlast/SMF2.1 that referenced this pull request Aug 24, 2026
SimpleMachines#9317 put the Docker environment upstream, so the copies of compose.yaml
and most of .docker/ that this branch was carrying are now identical to
what release-3.0 has. Merging drops them from the diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
albertlast added a commit to albertlast/SMF2.1 that referenced this pull request Aug 24, 2026
SimpleMachines#9317 put the Docker environment upstream, so the copies of compose.yaml
and most of .docker/ that this branch was carrying are now identical to
what release-3.0 has. Merging drops them from the diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Meta Repository tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants