Thank you for helping maintain this tModLoader container. Contributions should focus on container behavior, deployment, Workshop management, automation, documentation, or fixes that make the dedicated server more reliable.
This project is independently maintained. Submit issues and pull requests here; its features, fixes, and releases are managed in this repository.
- Read and follow the Code of Conduct.
- Search existing issues and pull requests for related work.
- Use a normal issue for bugs and feature requests.
- Use the private process in SECURITY.md for vulnerabilities.
- Discuss large behavior or compatibility changes before implementing them.
Start with the code and process map to trace startup, dashboard requests, staged settings, and background operations. It also records the remaining maintainability hotspots and their regression coverage.
Install Git, Docker Engine or Docker Desktop, and Bash. ShellCheck and actionlint are recommended; both can also be run from containers. On Windows, Git Bash is the simplest way to run the repository's Bash tests.
Build both linux/amd64 and linux/arm64 when changing image dependencies or
Workshop handling. ARM64 uses native DepotDownloader; AMD64 uses SteamCMD.
The CI matrix runs on native runners for both architectures. Docker Desktop
can test an ARM64 image on AMD64 via its built-in emulation, but those tests
do not measure native ARM performance.
For each image, run tests/architecture-test.sh and
tests/workshop-download-test.sh inside the container as tml, with the
repository mounted read-only at /repo. The latter downloads Recipe Browser
anonymously into a temporary directory and verifies that a second run reuses
the cache. Also run python3 -m unittest discover -s tests -p 'test_workshop_download.py' -v for download failure and cache replacement safety.
Create a branch from the current master, make focused changes, and preserve
backward compatibility unless the pull request clearly documents a necessary
breaking change.
For local Python tests, install argon2-cffi, waitress, cryptography, and PyYAML. Also run admin tests inside the
built image to verify compatibility with Ubuntu's packaged Argon2 library.
For the administration page, run python3 -m unittest discover -s tests -p 'test_admin.py' -v, node --check web/app.js, and node --check web/setup.js. Test the default WebUI-enabled container with
disposable volumes and a test token: first-run setup, saved-hash restart, authentication rejection, stage without
apply, confirmed apply/restart, backup/verify, and environment-mode read-only
behavior. Live Workshop search requires a separately supplied Steam API key;
mocked API tests do not establish live key access.
For setup field validation, run node tests/setup-ui-test.cjs with Playwright
installed. Set SETUP_TEST_URL and SETUP_TEST_CODE for a disposable first-run
server; this test creates its admin credential. Optionally set
SETUP_TEST_BROWSER=msedge to use installed Edge instead of Playwright Chromium,
and SETUP_TEST_SCREENSHOT to save the validated form preview.
The administration integration test also verifies recovery previews, checksum
rejection before downtime, live restore, retained originals, startup failure
and startup retry. Run node tests/admin-recovery-test.cjs with Playwright and
Chromium installed to exercise confirmation and recovery controls in a browser.
Set PLAYWRIGHT_CHANNEL=msedge to use an installed Edge browser instead.
Run node tests/admin-players-test.cjs for player filtering, moderation and
announcement confirmations, unavailable-state controls and narrow-screen layout.
Player parser and targeting tests are included in test_admin*.py; real empty
roster queries and announcement delivery are exercised by the admin integration
test. Connected-player kick/ban outcomes require a real game client.
Run node tests/admin-worlds-test.cjs to verify world drafts, cancellation,
confirmation and read-only controls. The administration integration test creates
a world through the Worlds API, switches to the original world, then back again.
For world creation, also run python3 -m unittest discover -s tests -p 'test_world_creation.py' -v and the configuration tests. Validate real generation
with disposable data for both TMOD_WORLDEVIL=corruption and crimson; check
the generation log and saved .wld/.twld files. Reuse the world name to check
that startup loads it without regeneration, then apply an unused name in web
mode to check creation during the save/stop/start workflow.
The image includes Python 3.12 for container-native backups. Run
python3 -m unittest discover -s tests -p 'test_backup*.py' -v on Linux. Also run
sudo python3 tests/backup-integration-test.py tmodloader:dev after building the
image. This creates disposable data and checks an actual server backup/restore
cycle with ownership and health verification.
Maintenance controls are covered by test_admin_operations.py and
test_admin_restart.py. Run node tests/admin-maintenance-test.cjs with
Playwright and Python available for the action buttons, countdown controls,
backup schedule form, and log retention settings. Run
python tests/maintenance-integration-test.py tmodloader:dev against local
Docker for real save/restart, countdown cancellation, and calendar backup
validation in disposable containers and volumes.
Run the checks relevant to your change. Runtime changes should pass the full set:
bash -n ./*.sh tests/*.sh
shellcheck --severity=warning autosave.sh container-init.sh entrypoint.sh healthcheck.sh inject.sh \
log-filter.sh manage-mods.sh prepare-config.sh run-server.sh tests/*.sh
docker compose config --quiet
docker build --tag tmodloader:dev .Run the script tests against the built image:
docker run --rm --user tml:tml --entrypoint bash \
--mount type=bind,source="$PWD",target=/repo,readonly \
tmodloader:dev \
/repo/tests/manage-mods-test.sh /terraria-server/manage-mods.sh
docker run --rm --user tml:tml --entrypoint bash \
--mount type=bind,source="$PWD",target=/repo,readonly \
tmodloader:dev \
/repo/tests/locale-test.sh /usr/bin/steamcmd
docker run --rm --user tml:tml --entrypoint bash \
--mount type=bind,source="$PWD",target=/repo,readonly \
tmodloader:dev \
/repo/tests/config-test.sh /terraria-server/prepare-config.sh
docker run --rm --user tml:tml --entrypoint bash \
--mount type=bind,source="$PWD",target=/repo,readonly \
tmodloader:dev \
/repo/tests/log-filter-test.sh /terraria-server/log-filter.sh /terraria-server/run-server.sh
docker run --rm --user tml:tml --entrypoint bash \
--mount type=bind,source="$PWD",target=/repo,readonly \
tmodloader:dev \
/repo/tests/runtime-control-test.sh /usr/local/bin/inject
bash tests/server-smoke-test.sh tmodloader:devThe smoke test creates temporary Docker resources, starts a real tModLoader server, verifies health and command injection, stops it, checks the exit status, verifies root initialization, repaired persistent-volume ownership, the low-privilege server identity and hardened runtime, and persistent logs. It can take several minutes on an uncached host.
When invoking bind mounts from PowerShell, replace $PWD with an absolute
Windows path. GitHub Actions runs the same build and runtime checks on pull
requests.
test_admin_updates.py covers release filtering, failed compatibility checks,
interrupted promotion, paired-data rollback, first-world failure handling and
recovery authorization. tests/admin-updates-test.cjs covers the dashboard notice,
diagnostics and recovery confirmation with deterministic API responses.
For a real upstream upgrade and rollback, build an image with an older supported stable release, then run the opt-in integration test (it creates and removes only disposable Docker resources):
docker build --build-arg TMOD_VERSION=v2026.06.3.6 -t tmodloader:updates-old-test .
python tests/runtime-update-integration-test.py tmodloader:updates-old-testRepeat with --platform linux/arm64 and a separate image tag when changing runtime
installation. This test contacts GitHub and expects a newer supported release.
It verifies a cached fallback survives container recreation and restores a paired
world checkpoint. Existing server lifecycle tests set TMOD_AUTO_UPDATE=0 so
upstream release timing cannot silently change their subject under test.
- Use
set -Eeuo pipefailin Bash scripts unless a documented compatibility reason prevents it. - Quote expansions and keep ShellCheck clean at warning severity.
- Validate user-controlled values before writing config files or commands.
- Never print passwords, tokens, or other secrets.
- Add regression coverage for fixed bugs and failure paths.
- Keep network-dependent tests out of the script test suite; use deterministic mocks there and reserve network/server behavior for the Docker smoke test.
- Update
README.md,.env.example, andCHANGELOG.mdwhen behavior or configuration changes.
VERSION contains the container's MAJOR.MINOR.PATCH SemVer core:
- Increment
MAJORfor incompatible configuration, data-layout, or operational changes. - Increment
MINORfor backward-compatible container features. - Increment
PATCHfor backward-compatible fixes and security updates.
Upstream tModLoader releases are installed by the runtime updater and do not
require a container version bump. If deliberately publishing a new image with
a different bundled runtime, use a new container version to preserve immutable tags.
Before publishing, increment VERSION, add a matching ## [X.Y.Z] - YYYY-MM-DD
section to CHANGELOG.md, and return ## [Unreleased] to an empty state.
Follow Keep a Changelog: newest releases
first, ISO dates, and human-readable entries under applicable Added, Changed,
Deprecated, Removed, Fixed, or Security headings. Omit empty categories
and update version comparison links. Generated GitHub release notes lead with
these changes and keep image/source/validation details in an expandable section.
Stable releases and images use X.Y.Z; previews use X.Y.Z-preview.
The bundled tModLoader version is recorded in release notes and OCI labels.
Numbered tags are immutable. The publisher runs when VERSION changes on
master, or through manual dispatch. It selects initial stable/preview runtimes
for that container release; it does not poll upstream on a schedule.
For Docker Hub credentials, initial publication, and upload retries, see
Docker Hub publishing. Validate mirror changes with
python3 -m unittest discover -s tests -p 'test_dockerhub.py' -v.
Use an imperative commit subject and explain the user-visible result, risks,
compatibility impact, and validation in the pull request. Do not manually move
public image tags as part of a contribution. After changes reach master, the
publisher builds and tests an untagged candidate digest before updating GHCR
tags. It then creates a GitHub Release from the matching versioned changelog
section. The automation refuses to reuse numbered tags; only latest and preview aliases move.
Dashboard browser regression tests also include tests/admin-profiles-test.cjs,
tests/admin-playthroughs-test.cjs, tests/admin-overview-test.cjs,
tests/admin-attention-test.cjs and tests/admin-unsaved-test.cjs.
Validate dashboard runtime recovery with python tests/dashboard-runtime-recovery-test.py IMAGE. This uses disposable data, confirms checkpoint restoration and game health, and verifies the authenticated dashboard remains responsive without changing container uptime.