# Space Merchant Realms runs as a Docker Compose stack with Flyway owning the schema

> The engine is PHP on Apache behind Traefik, with database changes arriving as numbered SQL files. Two operational details catch newcomers out: a game you create stays invisible until you flip a column in the database yourself, and an auto-refreshing page drops the request variables it needs to render.

**smrealms/smr** — Space Merchant Realms open-source game engine

- Repository: https://github.com/smrealms/smr
- Website: http://www.smrealms.de
- Stars: 28 · Forks: 20
- Language: PHP
- License: AGPL-3.0
- Published: 2026-08-27 · Updated: 2026-08-27 · Language: en
- Canonical page: https://hysenlabs.com/projects/smrealms-smr

## Three sample files must be copied by hand, and their values must agree

Installation starts with a clone and then three renames, because the repository ships samples rather than live configuration. The environment sample becomes the environment file, the MySQL client sample becomes the MySQL client file, and the specific config sample under the config directory becomes the live specific config. The documentation says the shipped defaults are sensible but should be adjusted, and adds a warning that is easy to skim past: the options have to be consistent across those separate files. That warning is not cosmetic, because the same names are consumed by different components. The compose file reads the environment file for the database credentials, for the host routing rule, and for the names of the base image and source services, and it reads the MySQL sample as the client's own config inside the database container.

```
docker compose up --build -d traefik smr
```

## Flyway owns the schema, and a patch is one file in a patches directory

The database is never created by hand. A dedicated migration service does it, and the same command initializes an empty database or brings an existing one up to date with new patches, which is why the documentation gives one line for both cases rather than separate initialise and upgrade steps. To change the schema, you add a file named with a version number, a double underscore, and a description, ending in a SQL extension, into the patches directory, and then rerun the command. The migration service is declared in the compose file as an extension of a shared base service and depends on the database, so it cannot run before the database it is migrating. That same database container is what the test suite needs: the unit test setup asks you to make sure it is running and ready before executing the suite, because integration cases talk to it.

```
docker compose run --rm flyway
```

If you want to look at the result in a browser instead of the command line, there is an optional admin container started with its own command and reachable under a path prefix on the local host.

```
docker compose up --build -d pma
```

## Admin rights begin as a normal account plus one shell script

There is no flag in the configuration file that creates an administrator, and no first-run prompt asking for one. The documented path is deliberately indirect: register an ordinary account through the site's own registration form, then run a script shipped in the repository's database directory to grant that account admin permissions.

```
db/init_admin.sh
```

Once the change is applied, the account sees an admin tools link on the left hand side while logged in, and that link is what lets you hand out any further permission, including to yourself or to other players. Two consequences are worth planning around. The script needs shell access into the environment rather than a web session, and the permission grant is tied to a specific account name, so an install where you cannot reach a shell is an install where the first account cannot be promoted.

## A new game stays invisible until you edit a column yourself

Creating a game is a two half operation and the documentation is unusually clear about where the seam is. You need a permission with a specific numbered name before the creation link will do anything, and once you have it the game is generated through the admin tools. At that point the game exists but players still cannot join it, because listing is gated on a database column rather than on the creation step. You have to edit the game table directly and set the enabled flag for your game to true. This is the single most common place where a first-time operator concludes the engine is broken. Plan for it by doing the table edit immediately after generating the game, and by treating the two halves as one task rather than something the admin tools finish for you.

## Auto-refreshed pages drop request variables but still demand them

Input reaches a page through a session-backed accessor rather than by reading the request directly, and the accessor family stores the value in the session so it survives the navigation. The subtlety is what happens on a page that refreshes itself with AJAX. The inputs are not resent when that happens, so the submitted values are not available again, yet the page still requires them to render correctly. In practice that means a field that looks empty on an auto-refresh is not evidence that the input was lost; it means the renderer is still being asked for a value the browser stopped sending. Anything you write against those accessors has to tolerate the empty case on the second render, and testing such a page with a single manual load will not reproduce the behaviour that shows up in normal use.

## Links are containers, not strings, so the hotkey system can hook them

Generating a URL by concatenating text is discouraged in favour of helpers on a globals object or on the relevant domain object, and the stated reason is not style: building links that way is clearer and it lets the engine's hotkey system intercept them. The pattern is to instantiate a page class as a container, then ask it for its address, which returns a value you can render as a link and whose target is that page.

```
$container = new CurrentSector();
```

The same container offers a forward call that sends the browser to the page inside the current request instead of emitting a link. Naming conventions support the separation: variables are camelCase in ordinary code but UpperCamelCase inside templates, and associative array indices are UpperCamelCase too, so container keys read as named fields rather than loose strings.

```
$container['SectorID'] = $sectorID;
```

Classes go in the namespaced library directory for the autoloader, and engine files with their templates live in the two parallel engine and template directories.

## Shared rules live in the abstract class and the game type overrides in the child

There is a pattern here that was originally built for one purpose and then repurposed. It started as a way to describe non-player characters in the conventional object oriented sense, but that idea was discarded. What replaced it is a rule about where shared behaviour belongs: all core and shared default code should live in the abstract version of a class, and the normal child class implements the parts specific to a game type or overrides them. The example given makes the intent concrete, with an account class that makes every account appear as a veteran account while one game type is being played. That arrangement is why game variants do not fork the engine. Read it as a placement rule rather than a polymorphic flourish, because the discarded original purpose is easy to mistake for the reason the pattern exists.

## One build flag decides php.ini, Xdebug, Composer packages and assets

A single argument set at image build time branches the rest of the installation. Its default of one selects the production php.ini, and setting it to zero selects the development file, installs the process control extension, installs a pinned Xdebug release, enables it, and points its profiler output at a dedicated directory that the compose file then mounts from the host. The same flag drives Composer, which is invoked with dev packages excluded when the flag is one and included when it is zero, and it is installed at a pinned version from the official installer. Assets are built in a separate earlier stage on a Node image, where a single script minifies and cache busts the stylesheets and scripts before the dependency directory is deleted. The unminified sources are deliberately kept in the runtime image so a development environment variable can select them at runtime instead of forcing a rebuild.

## Conclusion

Use this stack if you intend to run a Space Merchant Realms server or work on the engine, and you have Docker available, because there is no non-container path documented here. Before the first start, copy the three sample files and make their values agree, since the routing rule, image name and source mount all cross between them. After the first start, remember the two manual steps nobody automates for you: promoting an account with the admin script, and setting the enabled flag on a new game. For tests, bring the MySQL service up first or the integration cases will not have a database to talk to.

## FAQ

### What do I need to run Space Merchant Realms locally?

Docker version 19.03.0 or newer and the version 2 compose plugin, and Composer 2.0.5 or newer if you want to run unit tests on your own machine. Clone the repository, copy the environment, MySQL client and specific config samples to their live names, run the migration service once, then bring up the proxy and the engine.

### How do I make a game visible to players in SMR?

Register a normal account, run the admin script in the database directory to give it admin permissions, then use the admin tools link to grant yourself the numbered universe generator permission and create the game. The game still will not appear in the join list until you edit the game table and set its enabled flag to true.

### Can I run the SMR PHPUnit tests without Docker Compose taking over?

The suite runs through Composer, with the MySQL container started and ready first because integration tests that touch the database need it. Tests live in the test directory, and IDE-based editors can run the same suite by pointing a remote interpreter at the phpunit service and pasting the values from the environment file in the test directory.

### What naming and formatting conventions does SMR expect?

Opening braces go on the same line after a single space, single line if statements still get braces, functions are camelCase, classes are UpperCamelCase, and associative array indices are UpperCamelCase. Variables are camelCase in normal code but UpperCamelCase inside templates, and the project notes that not all existing code follows these guidelines yet.

## Sources

- [Official documentation](http://www.smrealms.de)
- [Official README](https://github.com/smrealms/smr#readme)
- [Project repository](https://github.com/smrealms/smr)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/smrealms-smr
