Getting started
Installing
One install serves one organisation: a league, a club or a venue operator, with its website, admin, API and the apps' backend. With an Extended license, each of your clients gets an install of their own (the last section).
What you need
- A domain for the platform, such as
sports.example.com, pointing at the server. - A Linux server with Docker (the simple way), or one with PHP 8.4 or newer (8.5 recommended) and the
extensions
ctype,gd,iconv,intl,pdo_mysqlandsodium, MySQL 8, Composer and Node 24 (only to build the website's styles). - A Mailgun sending domain, for the emails: sign-in codes, receipts, reminders. The platform runs without it, but nobody can reset a password.
- The license key from your purchase email.
With Docker
On the server, in the backend's folder:
touch .env.local
docker compose -f compose.yaml build
docker compose -f compose.yaml run --rm --no-deps app php bin/new-instance --docker \
--url=https://sports.example.com --brand="Harbour Sports" --from-email=noreply@sports.example.com
docker compose -f compose.yaml up -d
That starts three services:
| Service | What it does |
|---|---|
app | The website, the admin and the API, behind Caddy, which gets the HTTPS certificate for the --url domain by itself (ports 80 and 443 must be open). |
cron | The scheduled jobs: availability asks, reminders, account deletions, retention (Scheduled jobs and backups). |
database | MySQL 8.4, with its data in the db_data volume. Uploaded images are in the uploads volume. |
Pushes and emails go out as the page that sends them loads. To send them from a background queue instead, set
MESSENGER_TRANSPORT_DSN=doctrine://default in .env.local and start the worker service too:
docker compose -f compose.yaml --profile worker up -d (Scheduled jobs and backups, Background queue).
Without Docker
composer install --no-dev --optimize-autoloader
npm ci && npm run build
mysql -u root -p -e "CREATE DATABASE sports CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'sports'@'127.0.0.1' IDENTIFIED BY 'PASSWORD'; GRANT ALL ON sports.* TO 'sports'@'127.0.0.1'"
php bin/new-instance --url=https://sports.example.com --brand="Harbour Sports" \
--from-email=noreply@sports.example.com \
--database-url="mysql://sports:PASSWORD@127.0.0.1:3306/sports?serverVersion=8.0.32&charset=utf8mb4"
Then point the web server at the public/ folder, with PHP-FPM, and let requests up to 13 MB through (an
image upload is up to 10 MB). With Caddy, for example:
sports.example.com {
root * /var/www/sports/public
encode zstd gzip
request_body {
max_size 13MB
}
php_fastcgi unix//run/php/php-fpm.sock
file_server
}
Add the scheduled jobs to the crontab (Scheduled jobs and backups, Crontab). Pushes and emails go out as the page that sends them loads; a background queue is optional (Scheduled jobs and backups, Background queue).
What bin/new-instance does
It gives this copy its own secrets in .env.local: the app secret, which also seals the secret settings until
you give them a key of their own (Secret settings), the JWT passphrase and the public address; with Docker,
the database's passwords and the domain for HTTPS. It makes the API's signing keys in config/jwt/ and the
folder for uploads. It never touches an install that's already set up, and never changes keys: new keys would
sign everybody out.
--scheme=harboursports sets the link scheme of your app, when it isn't the default one. It must match the
scheme in your app's brand.json.
The installer
Open https://sports.example.com/install. Each step shows a green check once it's done:
- Server requirements: PHP's version and extensions, and the folders that must be writable. Your host enables what is missing.
- Database: already connected when
bin/new-instancewrote the database address. Otherwise enter the connection, and the installer saves it. - Prepare backend: makes the tables, the settings and the sport presets, from the browser, and the API's signing keys if they are missing.
- Platform settings: the platform's name, public address, currency, country, language and logos, and Mailgun if you have it now. Everything here can change later in the admin.
- Admin account: the first administrator.
- License: your key. It activates once, for this platform's domain. A Standard key runs one platform; an Extended key runs one for each of your clients.
- Finish: marks the platform installed, and closes the installer.
Then sign in at /admin and go through Configuring.
Before your first players arrive
- Emails: Settings, Mail & notifications: Mailgun's key and domain, and "Send emails" on. Register a test player and check that the code arrives.
- The apps' links: Settings, Website: the store links, and the identifiers that let invite links open the app
(Apple team ID, bundle ID, Android package and its SHA-256 fingerprint). They are in your app's
brand.jsonand its signing key. - Payments, when you take them: Payments.
- Privacy: check the privacy policy and the terms, and Settings, Privacy (Privacy and GDPR).
- Backups: a nightly copy, kept somewhere else than this server (Scheduled jobs and backups, Backups). Keep
.env.localandconfig/jwt/with them: without them, a restored database can't be read or signed into. - A check:
php bin/console app:health(with Docker:docker compose -f compose.yaml exec app php bin/console app:health) checks the database, the migrations, the settings, the signing keys, the writable folders and the API.
New releases are taken with bin/upgrade (Upgrading). The admin's Platform updates page says when one is
out.
Another client's platform
With an Extended license, each client gets their own install: their own copy of the backend (a folder, or a
server), their own database, domain and bin/new-instance, and the same license key in step 6. Their app is a
brand of its own (Your brand), packed with their name, colours and store accounts. Nothing is shared
between two clients' platforms.
When something goes wrong
| What you see | Where to look |
|---|---|
| An error page, or the app can't sign in | The admin's Application logs, or var/log/prod.log; then app:health. |
| The installer says a folder isn't writable | The web server's user must own var/ and public/uploads/. |
| Images don't upload | The web server's body size limit (13 MB above), and public/uploads/ writable. |
| Emails don't arrive | Settings, Mail & notifications: "Send emails" on, and Mailgun's region matches your domain's. |
| Pushes don't arrive | The app's Expo project (Building and publishing the apps), and the Expo access token in Settings, Mail & notifications if that project has Enhanced Security. |
| "License" stays red | The key from the purchase email, and the server can reach sharkhex.com. A Standard key runs one platform at a time: to move it, reply to the purchase email. |
Found a mistake? Tell us.