These six scripts handle everything needed to start, stop, and manage the contest system. Instead of typing one very long command with confusing flags (easy to mistype and hard to remember), you run a simple script. Each script does one thing and gives you clear feedback about what's happening.
-
Docker must be installed. Check by opening a terminal and running
docker --version. It should print a version number. If you see "command not found," install Docker from https://docs.docker.com/get-docker/. -
Create a
.envconfiguration file. Copy the example file with this command:cp .env.example .env
Then open
.envin a text editor (nano, gedit, VS Code, or whatever you normally use) and change every value markedCHANGE_ME. At minimum:CMS_DB_URLpassword,POSTGRES_PASSWORD,CMS_SECRET_KEY,CMS_ADMIN_USER, andCMS_ADMIN_PASSWORD.
Follow these steps to start the contest system for the first time:
-
Open a terminal in the folder where the scripts live (the project root).
-
Run the startup script:
./up.sh
-
Answer the first question:
Use local database (Docker)? [y/N]- Answer
yif you want these scripts to manage the database (most common for new setups). - Answer
nonly if you already have a PostgreSQL database running somewhere else.
- Answer
-
Answer the second question:
Rebuild image? [y/N]- Answer
n(faster). Only answeryafter updating the code.
- Answer
-
Wait for startup. You'll see output like:
[+] Running 4/4 ✓ Container cms-prod-db-1 Created ✓ Container cms-prod-db-init-1 Created ✓ Container cms-prod-cms-1 Created ✓ Container cms-prod-cws-1 CreatedThe system takes about 30-60 seconds to fully start. Services may not respond immediately.
-
Check the status once the script finishes:
./status.sh
You should see several containers with status
Up. If any sayExitorExited, something went wrong — check./logs.shfor error messages. -
Open your browser and try these URLs (assuming default ports):
- Contestants log in here: http://localhost:8888
- Contest administration: http://localhost:8889
- Public scoreboard: http://localhost:8890
If the pages don't load, the system might still be starting up. Wait another 30 seconds and try again, or run ./logs.sh to see what's happening.
Starts the contest system. Asks two questions before starting: whether to use a local database and whether to rebuild the Docker image. Run this when the system is stopped.
./up.shStops all running services. The data is preserved — you can run ./up.sh again to start back where you left off.
./down.shShows whether all services are running correctly. Lists each container (a lightweight virtual environment) with its current state. All should say Up.
./status.shShows a live stream of what every service is doing — useful for debugging problems. Press Ctrl+C to stop watching and return to the terminal prompt.
./logs.shStops and immediately starts the system again. Use this after editing the .env config file to apply changes, or if services seem stuck.
./restart.shChanges which contest is currently active. It shows you the list of contests already in the system and lets you pick one by ID. After changing, it can automatically restart the services to apply the change.
./contest.shClears ranking data from the running container. Asks what to delete — results (submissions and subchanges), users, or tasks and contests — and whether to regenerate the ranking from the current contest data in the database. Only affects the scoreboard; contestant submissions and scores stored in PostgreSQL are never touched.
./clear-ranking.shIf you choose to regenerate, ProxyService is restarted and will re-push all scored submissions to the ranking. Scores appear on the scoreboard within ~6 minutes.
Creates a backup of contest data as a .zip file in the dumps/ folder. Asks which contests to back up, the filename to use, and whether to leave out submissions, user accounts, or generated files.
./export.shThe backup file is saved to dumps/ at the project root and is ready to use immediately after the script finishes.
Restores contest data from a .zip backup file created by export.sh. Lists the available backups in dumps/, lets you pick one, and walks you through the options — including whether to wipe the database first (useful for a full restore from scratch).
./import.shWarning: Choosing to wipe the database before importing will permanently delete all existing contest data. Only do this when you are sure you want to restore from the selected backup.
The CMS_PROJECT_NAME variable in .env (default: cms-prod) is used to group Docker containers on your machine. If you run only one copy of CMS, leave it as is. If you need to run two separate CMS setups on the same machine (for example, testing and production), change this to a different short name for the second one — something like cms-test or cms-staging. This prevents containers from different instances from conflicting. Keep it short and use lowercase letters and hyphens only.
By default, a single cmsContestWebServer instance handles all contestant traffic. If you
need more capacity — typically for contests with hundreds of simultaneous users — you can
run several instances behind a reverse proxy.
Set CMS_CWS_COUNT in .env to the number of shards you want. Each shard listens on a
consecutive port starting from CMS_CWS_HTTP_PORT:
| Shard | Port |
|---|---|
| 0 | CMS_CWS_HTTP_PORT (e.g. 8888) |
| 1 | CMS_CWS_HTTP_PORT + 1 (e.g. 8889) |
| 2 | CMS_CWS_HTTP_PORT + 2 (e.g. 8890) |
All shard ports are automatically exposed on the host when you start with ./up.sh.
With the defaults (CMS_CWS_HTTP_PORT=8888, CMS_AWS_HTTP_PORT=8889, CMS_RWS_HTTP_PORT=8890), setting
CMS_CWS_COUNT > 1 puts shards on ports that may collide with other servers.
When CMS_CWS_COUNT > 1, move both CMS_AWS_HTTP_PORT and CMS_RWS_HTTP_PORT above the CWS range:
CMS_CWS_COUNT=3
CMS_CWS_HTTP_PORT=8888 # shards: 8888, 8889, 8890
CMS_AWS_HTTP_PORT=8891 # must be >= CMS_CWS_HTTP_PORT + CMS_CWS_COUNT
CMS_RWS_HTTP_PORT=8892 # must also be outside the CWS shard range
Your reverse proxy must balance incoming contestant requests across all shards. Here is a
minimal nginx configuration for three shards (CMS_CWS_COUNT=3, base port 8888). Adapt
the port numbers to match your .env:
upstream cws {
ip_hash; # required: keeps each contestant on the same shard
server 127.0.0.1:8888;
server 127.0.0.1:8889;
server 127.0.0.1:8890;
}
server {
listen 80;
server_name contest.example.com;
location / {
proxy_pass http://cws;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
ip_hashis required. Without it, a contestant's requests may land on different shards and their session will be lost. CMS stores session state in-process, not in a shared store.
Also set CMS_NUM_PROXIES_USED=1 in .env so CMS logs the real contestant IP addresses
instead of the proxy's address.
After changing .env, run ./restart.sh to apply.
The services might still be starting up — they can take 30-60 seconds to fully initialize. Run ./status.sh to see if all containers say Up. If they do, wait a bit longer and try the page again. If some containers show Exit or Exited, run ./logs.sh to see what error caused them to fail.
No contests have been imported yet. Use the Admin interface at http://localhost:8889 to create a new contest or import an existing one. Once you've imported a contest, run ./contest.sh again and you should see it in the list.
CMS_SECRET_KEY in your .env file must be a 32-character hexadecimal string (16 bytes). If you generated it with a tool that produces Base64 output (characters like /, +, or =), the admin server will crash with this error.
Generate a valid key and update .env in one command:
NEW_KEY=$(openssl rand -hex 16) && sed -i "s|CMS_SECRET_KEY=.*|CMS_SECRET_KEY=${NEW_KEY}|" .env && grep CMS_SECRET_KEY .envThen restart with ./restart.sh.
CMS_CONTEST_ID is required — without it, supervisord.conf is not generated and no services start at all. On a fresh install the contest does not exist in the database yet, so cmsContestWebServer and cmsProxyService will crash-loop. This is normal.
The admin server (cmsAdminWebServer) still starts and is available at port 8889. Use it to create or import your first contest. Once the contest exists, supervisord retries the failed services automatically. If they do not recover within a minute, run ./restart.sh.
Afterwards, run ./contest.sh to confirm the ID is correct.
The script file doesn't have execute permission. Fix it by running:
chmod +x <script-name>.shFor example: chmod +x up.sh. After this, you can run the script normally.