Docker Compose for Mediawiki + OpenSemanticLab
Minimal Setup
- 4 CPUs ( > 2 GHz)
- 4GB RAM
- 50 GB HDD
Recommended:
- 8 CPUs,
- 8 GB RAM
- 100 GB SSD
OS: Any OS with support for Docker, e.g. Ubuntu in its current LTS version (24.04.3)
Required
Recommended to follow instructions:
Optional SSL Termination
- Nginx for SSL Termination + Certbot to create SSL/TSL certs with Let's Encrypt
- or (recommended) caddy-docker-proxy as integrated service, example config see
docker-compose.caddy.example.override.yml
Clone & init the repo
git clone https://github.com/OpenSemanticLab/docker-compose-osl-wiki
cd docker-compose-osl-wiki
sudo chown -R www-data:www-data mediawiki/dataCopy .env.example to .env and CustomSettings.php.example to CustomSettings.php
cp .env.example .env
cp mediawiki/config/CustomSettings.example.php mediawiki/config/CustomSettings.php
Set the config parameters in .env
Example:
COMPOSE_PROJECT_NAME=osl-1 # unique project name (change when running multiple instances)
MW_IMAGE_TAG=main # Docker image tag, see table below
MW_HOST_PORT=8081 # the port mediawiki exposes on the host (localhost only)
MW_SITE_SERVER=http://localhost:8081 # the public URL of your wiki
MW_SITE_NAME=Wiki # the name of your instance
MW_SITE_LANG=en # the site language
MW_TIME_ZONE=Europe/Berlin # your time zone
MW_ADMIN_PASS=change_me123 # the password of the 'Admin' account
MW_DB_PASS=change_me123 # the db password of the user 'mediawiki'
# the packages to install (multi-line)
MW_PAGE_PACKAGES="
world.opensemantic.core;
world.opensemantic.base;
world.opensemantic.demo.common;
"
MW_AUTOIMPORT_PAGES=true # if true, packages are installed / updated at start
MW_AUTOBUILD_SITEMAP=false # if true, the sitemap is periodically built
MYSQL_ROOT_PASSWORD=change_me123 # the password of the 'root' accountAvailable image tags (MW_IMAGE_TAG):
| Tag | MediaWiki | Description |
|---|---|---|
main |
REL1_43 | Latest stable build (default) |
latest |
REL1_43 | Alias for main |
v2.x.x (e.g. v2.0.0-alpha.1) |
REL1_43 | Versioned releases for MW 1.43 |
v1.x.x (e.g. v1.0.0-beta.23) |
REL1_39 | Versioned releases for MW 1.39 |
v0.x.x (e.g. v0.8.0) |
REL1_35 | Legacy releases for MW 1.35 |
Note
By default, no ports are exposed on the host. Services communicate over the internal Docker network. Use a reverse proxy (nginx/caddy) to expose mediawiki publicly, or use the local override for development:
cp docker-compose.local.example.override.yml docker-compose.override.ymlThis exposes mediawiki (:8081), mysql (:3307), blazegraph (:9999), and drawio (:8082) on the host.
You can customize the stack further with docker-compose.override.yml. Example overrides:
Expose ports for local development — see docker-compose.local.example.override.yml
Add caddy as reverse proxy — see docker-compose.caddy.example.override.yml
Mount custom volumes (logos, extensions):
services:
mediawiki:
volumes:
- ./mediawiki/config/logo.png:/var/www/html/w/logo.png
- ./mediawiki/config/logo.svg:/var/www/html/w/logo.svg
- ./mediawiki/extensions/MyCustomExtension:/var/www/html/w/extensions/MyCustomExtensiondocker compose upDepending on the size of the packages defined in MW_PAGE_PACKAGES it will take some time to install them in the background.
You can now login (e. g. at http://localhost:8081/wiki/Main_Page) with user 'Admin' and the MW_ADMIN_PASS you set in the .env file.
You can add or overwrite mediawiki settings by editing mediawiki/config/CustomSettings.php.
You need to re-run docker compose up to apply them
To make your instance public readable add:
####### Make it public ########
$wgGroupPermissions['*']['read'] = true;Default settings allow only members of the sysadmin group to delete pages.
# allow every user to delete pages
$wgGroupPermissions['user']['delete'] = true;Please note that deleted pages are still available in the pages archive.
Further settings and custom groups can also be defined, see Manual:User_rights. Example:
// revoke edit-right for standard users
$wgGroupPermissions['user']['edit'] = false;
$wgGroupPermissions['sysop']['edit'] = true;
$wgGroupPermissions['active-user'] = $wgGroupPermissions['user'];
// grant edit-right and delete-right for custom-group
$wgGroupPermissions['custom-group']['edit'] = true;
$wgGroupPermissions['custom-group']['delete'] = true;Pages installed via packages are default only editable for sysadmin. Custom schemas in the namespace Category can be further restricted by
// create schema-edit right
$wgAvailableRights[] = 'schema-edit';
// grant it to custom-group and sysop
$wgGroupPermissions['custom-group']['schema-edit'] = true;
$wgGroupPermissions['sysop']['schema-edit'] = true;
// restrict the creation of new categories outside installed packages
$wgNamespaceProtection[NS_CATEGORY] = ['schema-edit'];Please note: Content packages defined by MW_PAGE_PACKAGES will be install automatically.
Optional packages listed here can be installed under <your wiki domain>/wiki/Special:Packages. Package sources are hosted here.
To add additional optional packages, add
$wgPageExchangePackageFiles[] = 'packages.json url';e. g.
$wgPageExchangePackageFiles[] = 'https://raw.githubusercontent.com/OpenSemanticWorld-Packages/world.opensemantic.meta.docs/main/packages.json';
to mediawiki/config/CustomSettings.php
In order to add multiple packages that are listed in an index file, add it to the config as follows:
$wgPageExchangeFileDirectories[] = 'https://raw.githubusercontent.com/<MyOrg>/PagePackages/refs/heads/main/package_index.txt';For private repos generate a Github private repo access token with permission "Content" (read)
$wgPageExchangeGitHubAccessToken = [
'<MyOrg>' => 'github_pat_...', # org-level
'<MyOrg>/'<repo>' => 'github_pat_...', # repo-level
];In all cases additional packages are now available for installation. Use <your wiki domain>/wiki/Special:Packages or the API to actually install them (more information see Extension:Page_Exchange).
Insecure in public instances!
Example:
$additionalFileExtensions = [ 'py', 'exe' ];
$wgFileExtensions = array_merge( $wgFileExtensions, $additionalFileExtensions );
$wgProhibitedFileExtensions = array_diff( $wgProhibitedFileExtensions, $additionalFileExtensions );
$wgMimeTypeExclusions = array_diff( $wgMimeTypeExclusions, [ 'application/x-msdownload' ]); # for .exe
# allow any upload - insecure in public instances!
# $wgStrictFileExtensions = false;
# $wgCheckFileExtensions = false;
# $wgVerifyMimeType = false;If your instance is public, make sure to add a privacy policy to /wiki/Site:Privacy_policy and legal informations to /wiki/Site:General_disclaimer.
You may also create a single page with all necessary informations and point with a redirect from other pages to it: #REDIRECT [[Site:General_disclaimer]]
If you don't have an email server yet (optional, but necessary for notification and password resets, etc.), you can use docker-mailserver
The following extensions are bundled but not enabled by default. Enable them by adding the corresponding line to mediawiki/config/CustomSettings.php:
# Authentication
wfLoadExtension( 'OATHAuth' ); # Two-factor authentication (see Two-Factor-Authentication section)
wfLoadExtension( 'PluggableAuth' ); # Pluggable authentication framework
wfLoadExtension( 'OpenIDConnect' ); # OpenID Connect login (e.g. via Keycloak)
wfLoadExtension( 'Realnames' ); # Display real names beside user IDs
wfLoadExtension( 'ConfirmAccount' ); # Requires approval for new account requests
# Content & Moderation
wfLoadExtension( 'ApprovedRevs' ); # Allows setting approved revisions of pages
wfLoadExtension( 'CommentStreams' ); # Discussion comments on pages
wfLoadExtension( 'Lockdown' ); # Restrict namespace access per group
wfLoadExtension( 'HitCounters' ); # Page view counters
wfLoadExtension( 'UrlGetParameters' ); # Access URL parameters in wiki pages
wfLoadExtension( 'AbuseFilter' ); # Automated abuse detection and prevention
# Anti-Spam (for public instances)
wfLoadExtension( 'ConfirmEdit' ); # CAPTCHA for edits
wfLoadExtension( 'SpamBlacklist' ); # Block known spam URLs
# Semantic Web
wfLoadExtension( 'SemanticExtraSpecialProperties' ); # Exposes extra properties (creator, approved status, etc.)
# UI & Display
wfLoadExtension( 'Iframe' ); # Embed external content via iframes (see Iframes section)
wfLoadExtension( 'PagedTiffHandler' ); # Multi-page TIFF file support
wfLoadExtension( 'InteractiveSemanticGraph2' ); # Interactive graph visualization (v2)
# Data & Export
wfLoadExtension( 'WebDAV' ); # Access uploaded files via WebDAV (e.g. directly with MS Word/Excel)
wfLoadExtension( 'RdfExport' ); # DCAT catalog and OWL ontology export (public instances only, requires SPARQL store)
wfLoadExtension( 'Chatbot' ); # AI chatbot integration
wfLoadExtension( 'ApiGateway' ); # API gateway for external service integrationSee mediawiki/config/CustomSettings.php for a working example. Below are some common configurations:
CommentStreams — enable discussion threads on pages:
wfLoadExtension( 'CommentStreams' );
$wgCommentStreamsEnableVoting = true;
$wgCommentStreamsAllowedNamespaces = [
NS_MAIN, NS_USER, NS_FILE, NS_CATEGORY,
7000, // Item
];
$wgCommentStreamsInitiallyCollapsedNamespaces = $wgCommentStreamsAllowedNamespaces;ApprovedRevs + SemanticExtraSpecialProperties — track approved revisions and page metadata:
wfLoadExtension( 'ApprovedRevs' );
$egApprovedRevsShowApproveLatest = true;
$egApprovedRevsShowNotApprovedMessage = true;
wfLoadExtension( 'SemanticExtraSpecialProperties' );
$sespgEnabledPropertyList = [
'_EUSER', // Last editor
'_CUSER', // Page creator
'_APPROVED', // Approved revision
'_APPROVEDBY', // Approved by user
'_APPROVEDDATE', // Approval date
'_APPROVEDSTATUS', // Approval status
];WebDAV — access uploaded files directly with MS Office / LibreOffice:
wfLoadExtension( 'WebDAV' );Account management via OpenID Connect (e.g. Keycloak, ORCID):
wfLoadExtension( 'PluggableAuth' );
$wgPluggableAuth_EnableAutoLogin = false; // auto-login on visit
$wgPluggableAuth_EnableLocalLogin = true; // allow local password login
$wgPluggableAuth_EnableLocalProperties = true; // allow users to edit email/realname
wfLoadExtension( 'OpenIDConnect' );
$wgGroupPermissions['*']['autocreateaccount'] = true; // required for PluggableAuth
$wgHooks['BeforePageDisplay'][] = function( OutputPage &$out, Skin &$skin ) {
$out->addInlineStyle("#pt-createaccount { display: none;}"); // hide misleading "Create Account" link
};
wfLoadExtension( 'Realnames' ); // display real names instead of OIDC subject IDs
// Example: ORCID as identity provider
// See https://github.com/ORCID/ORCID-Source/blob/development/orcid-web/ORCID_AUTH_WITH_OPENID_CONNECT.md
$wgPluggableAuth_Config['Login with your ORCID Account'] = [
'plugin' => 'OpenIDConnect',
'data' => [
'providerURL' => 'https://orcid.org',
'clientID' => 'APP-...',
'clientsecret' => '...',
'scope' => ['openid'],
'preferred_username' => 'sub'
]
];
// Optional: restrict login to specific users
/*
$wgHooks['PluggableAuthUserAuthorization'][] = function( MediaWiki\User\UserIdentity $user, bool &$authorized ) {
$validUsernames = [
'0000-0003-0410-3616' // Simon Stier
];
$authorized = in_array($user->getName(), $validUsernames);
};
*/To debug authentication issues, add to CustomSettings.php:
$wgDebugLogGroups['PluggableAuth'] = '/var/www/html/w/cache/pluggableauth.log';
$wgDebugLogGroups['OpenIDConnect'] = '/var/www/html/w/cache/oidc.log';Then check with: docker compose exec mediawiki cat /var/www/html/w/cache/oidc.log
QLever SPARQL store — use QLever instead of Blazegraph:
$smwgSparqlRepositoryConnector = 'sparql11';
$smwgSparqlEndpoint["query"] = 'http://qlever:7001';
$smwgSparqlEndpoint["update"] = 'http://qlever:7001';
$smwgSparqlEndpoint["data"] = '';Run with: docker compose --profile qlever up
DrawIO diagrams may contain embedded SVG images (mostly from the built in icon library) using data:image/svg+xml URIs. MediaWiki blocks these by default as a security measure (embedded SVGs could contain scripts). If you need this feature, add to mediawiki/config/CustomSettings.php:
$wgAllowSvgDataUriInSvg = true;Warning: This reduces upload security. Only enable this on trusted instances where all uploaders are authenticated.
sudo cp misc/reverse_proxy_nginx.conf /etc/nginx/sites-enabled/default
sudo nano /etc/nginx/sites-enabled/default-> set domain and cert paths
Extension:Iframe enabled. To do so, add the following to your CustomSettings.php
wfLoadExtension( 'Iframe' );
$wgIframe['width'] = "100%"; # example for a default setting
$wgIframe['server']['example'] = [ 'scheme' => 'https', 'domain' => 'example.com' ];
$wgIframe['server']['dashboard'] = [ 'scheme' => 'https', 'domain' => 'user:password@example.com' ]; # example for basic auth
$wgIframe['server']['localhost:20200'] = [ 'scheme' => 'http', 'domain' => 'localhost:20200' ]; # to allow users to test a local running webappTo make use of the whitelisted domains, e.g. as https://subdomain.example.com/example/page&hl=e, add the following to any wiki page or template:
<iframe key="example" level="subdomain" path="example/page&hl=en" /># 2FA, see https://www.mediawiki.org/wiki/Extension:OATHAuth
wfLoadExtension( 'OATHAuth' );
$wgGroupPermissions['user']['oathauth-enable'] = true;
# $wgOATHRequiredForGroups = ['user']; # this will enforce 2FA but can only be applied in private wikis after every user activated it
# make sure to persist $wgSecretKey between updates, otherwise user need to re-register
$wgSecretKey = "...";Currently the default is blazegraph as SPARQL-Store. Since blazegraph is no longer maintained we are transitioning to use Apache Jena Fuseki. To switch to Fuseki, add the following settings to your CustomSettings.php file:
$smwgSparqlRepositoryConnector = 'fuseki';
$smwgSparqlEndpoint["query"] = 'http://fuseki:3030/ds/sparql';
$smwgSparqlEndpoint["update"] = 'http://fuseki:3030/ds/update';and run the stack with
docker compose --profile fuseki upNote: A full data rebuild is required to populate the new store.
to run include sparklis SPARQL editor, run
docker compose --profile fuseki --profile sparklis upor
COMPOSE_PROFILES=fuseki,sparklis docker compose upIf you do not need a SPARQL endpoint, you can switch to SMWElasticStore by reusing the elasticsearch container:
$smwgDefaultStore = 'SMWElasticStore';
$smwgElasticsearchEndpoints = [
[
'host' => 'elasticsearch',
'port' => 9200,
'scheme' => 'http'
]
];Note: Switch store types requires to re-setup the store.
php /var/www/html/w/extensions/SemanticMediaWiki/maintenance/setupStore.php
php /var/www/html/w/extensions/SemanticMediaWiki/maintenance/rebuildElasticIndex.php
php /var/www/html/w/extensions/SemanticMediaWiki/maintenance/rebuildData.phpRun the following commands inside the mediawiki container if you run in one of the following problems
- missing semantic properties after backup restore
php /var/www/html/w/extensions/SemanticMediaWiki/maintenance/rebuildData.php- no search results after backup restore
php /var/www/html/w/extensions/CirrusSearch/maintenance/ForceSearchIndex.php- incorrect link labels (page name instead of display name) after template changes or large imports
php /var/www/html/w/maintenance/refreshLinks.php- missing thumbnails for tif images
php /var/www/html/w/maintenance/refreshImageMetadata.php --force- Error when deleting a file
Error deleting file: Could not create directory "metastore/local-backend/local-deleted/v1/"
Fix the permission on the host
sudo chown -R www-data:www-data mediawiki/dataLarge mysql binlog files (see https://askubuntu.com/questions/1322041/how-to-solve-increasing-size-of-mysql-binlog-files-problem)
List files
docker compose exec db /bin/bash -c 'exec echo "SHOW BINARY LOGS;" | mysql -uroot -p"$MYSQL_ROOT_PASSWORD"'Delete files
docker compose exec db /bin/bash -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"'
mysql> PURGE BINARY LOGS TO 'binlog.000123';Docker log file size is unlimited in the default settings, see https://stackoverflow.com/questions/42510002/docker-how-to-clear-the-logs-properly-for-a-docker-container
To inspect the file size, run
du -sh -- /var/lib/docker/containers/*/*-json.logTo reset those file (remove all content), run
truncate -s 0 /var/lib/docker/containers/**/*-json.logTo change the setting, adapt /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "1g",
"max-file": "1"
}
}mkdir backup
docker compose exec db /bin/bash -c 'mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD" 2>/dev/null | gzip | base64 -w 0' | base64 -d > backup/db_backup_$(date +"%Y%m%d_%H%M%S").sql.gz
tar -zcf backup/file_backup_$(date +"%Y%m%d_%H%M%S").tar mediawiki/dataTo reset your instance and destroy all data run
docker compose down -v
sudo rm -R mysql/data/* && sudo rm -R blazegraph/data/* && sudo rm -R mediawiki/data/*
docker compose upThis is also required if you change the database passwords after the first run.
Reset your instance first, then import your backup. Make sure to set file and folder permissions afterwards.
zcat backup/db_backup_<date>.sql.gz | docker compose exec -T db sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"'
tar -xf backup/file_backup_<date>.tar
sudo chown -R www-data:www-data mediawiki/dataIf the database dump was created with different passwords (e.g. restoring on a new instance), update the MySQL passwords to match your .env configuration:
docker compose exec db mysql -uroot -p -e "
FLUSH PRIVILEGES;
ALTER USER 'root'@'%' IDENTIFIED BY '$(read -sp 'New root password: ' p && echo $p)';
ALTER USER 'mwuser'@'%' IDENTIFIED BY '$(read -sp 'New mwuser password: ' p && echo $p)';
"Enter the old root password when prompted by mysql -p, then the new passwords matching MYSQL_ROOT_PASSWORD and MW_DB_PASS from your .env file. Restart with docker compose restart afterwards.
To build the MediaWiki image locally instead of pulling the pre-built one:
docker compose build
docker compose upcd /mediawiki/build
docker buildx build --platform=linux/amd64,linux/arm64 --push -t docker.io/opensemanticlab/osl-mw:main-arm64 .MediaWiki's core config file LocalSettings.php is created dynamically on every container by merging
- InstallSettings.php
- DockerSettings.php
- CustomSettings.php
InstallSettings.php is created by running maintenance/install.php with parameters defined in .env on the first run. To recreate this file after change settings in .env set
environment:
- MW_REINSTALL=trueDockerSettings.php is copied from mediawiki/config/DockerSettings.php into the container during build.
CustomSettings.php can be mounted to the container (optional)
volumes:
- ./mediawiki/config/CustomSettings.php:/var/www/html/w/CustomSettings.phpTo modify LocalSettings.php without restarting the container, copy the merged file and mount it, this will skip the dynamical creation:
docker compose cp mediawiki:/var/www/html/w/LocalSettings.php mediawiki/config/LocalSettings.phpin docker-compose.yml:
volumes:
- ./mediawiki/config/LocalSettings.php:/var/www/html/w/LocalSettings.phpcheck for modificated extensions
cd /var/www/html/w/extensions/
find . -maxdepth 1 -mindepth 1 -type d -exec sh -c '(echo {} && cd {} && git status -s && echo)' \;create debug file
touch /var/www/html/w/my-custom-debug.log
chown www-data:www-data /var/www/html/w/my-custom-debug.login LocalSettings.php:
$wgDebugLogFile = "/var/www/html/w/my-custom-debug.log";in PHP source code:
wfDebug( "\n[tag] some debug message: $somevar.\n" );remove and recreate logfile
rm /var/www/html/w/my-custom-debug.log && touch /var/www/html/w/my-custom-debug.log && chown www-data:www-data /var/www/html/w/my-custom-debug.logcopy files
docker compose cp mediawiki/config/pub/. mediawiki:/var/www/html/w/pub/backup extensions
docker compose exec -T mediawiki tar -czf - -C /var/www/html/w/extensions/ . > backup/extensions_backup_$(date +"%Y%m%d_%H%M%S").tartriggers CI/CD workflow and pushes image with tags to docker registry (see also stackoverflow: push-git-commits-tags-simultaneously )
git tag <tag>
git push --atomic origin main --tags
Note: You may have to wait 15 - 30 min for all page packages to be installed on the first run
Pull reqired images (see ./tests/codecept/browsers.json) from docker registry before running codeceptjs:
manually
docker pull selenoid/video-recorder:latest-release;
docker pull selenoid/firefox:latest;
...automated by parsing ./tests/codecept/browsers.json (replace docker run --rm -i imega/jq with jq if installed on your host), see docs
docker pull selenoid/video-recorder:latest-release && cat ./tests/codecept/browsers.json | docker run --rm -i imega/jq -r '..|.image?|strings' | xargs -I{} docker pull {}Note: use kiosk mode for demo video recording
Run all tests with a single browser
docker compose run --rm codeceptjsRun only test with tag @<tag> a single browser
docker compose run --rm codeceptjs codeceptjs run --grep "@<tag>"Run only test with without @<tag> a single browser
docker compose run --rm codeceptjs codeceptjs run --grep "@<tag>" --invertRun multi-browser tests
docker compose run --rm codeceptjs codeceptjs run-multiple --allMore options: https://codecept.io/commands/
You can follow the test execution on selenoid-ui at "http://localhost:8080". Run with autopause to interact with the browser in a state where test have failed
- codeceptjs: container name
- codeceptjs: shell command inside container
docker compose run --rm codeceptjs codeceptjs run -p pauseOnFailCreate a new file ./tests/codecept/tests/_test.js
Follow the existing examples or https://codecept.io/tutorial/
To find XPath expressions and test them in the browser: https://stackoverflow.com/questions/41857614/how-to-find-xpath-of-an-element-in-firefox-inspector
To compare / assert values: https://github.com/SitamJana/codeceptjs-chai