A secure, web-based implementation of the classic Conquer strategy game using Docker containers, ttyd (terminal over HTTP), and Apache as a reverse proxy with SSL termination.
| Server | What is there |
|---|---|
| conquer.vejeta.com | The live game, run by the author of this project: a shared world with daily turns. Try it with no account (a practice world in your browser); to join, ask for an invite code at the address on the home page. In English, Spanish, German, Portuguese, Polish, Russian and Chinese. |
Running your own server? Open a pull request to add it here.
![]() |
![]() |
![]() |
| Trailer 40 seconds: what Conquer is |
Found your nation the game's own builder, step by step |
Your first turn the narrated tutorial |
The videos are narrated in all seven languages, with captions: the site
plays each visitor's own (web/tutorial/<video>.<language>.mp4, made by
tools/tutorial/video.py).
Everyday commands for the administrator and players: QUICK-REFERENCE.md.
This setup allows multiple players to access the same Conquer game instance through their web browsers, with proper authentication, rate limiting, and security features for safe public deployment.
Important: Conquer requires pre-generated world data to run. See the World Generation section below for setup instructions.
For development work on your local machine:
# Setup environment
./setup-environment.sh
# Choose option 1: Local development
# Start local development environment (loads config/local.env,
# creates the self-signed certificate and data/lib/ if missing)
./start-local.sh- URL: https://conquer.local (landing page), https://conquer.local/play/ (game)
- Setup: Full Docker environment (Apache + Conquer containers)
- SSL: Self-signed certificate (accept browser warning)
For deployment on a VPS with existing Apache:
# Setup environment
./setup-environment.sh
# Choose option 2: VPS production
# Deploy to VPS (run as root)
sudo ./deploy-to-vps.sh- URL: https://your-configured-domain.com (landing page),
/play/(game) - Setup: Host Apache + Conquer container
- SSL: Let's Encrypt certificate
https://your-domain/– public landing page (web/index.html), in English and Spanish (browser language, EN/ES switch or?lang=es): the current turn, the next turn update with a countdown, scores, the latest world news, what Conquer is, how to join, how turns work and a key reference.https://your-domain/guide.html– the player's guide (web/guide.html, screenshots inweb/guide/): the history of Conquer since 1987, a step-by-step tutorial of the whole workflow, advice for new rulers and veterans, the tournament formats with theirTURN_SCHEDULEsettings, and the season plan towards the game's 40th anniversary. Linked from the landing page. In Spanish atguide.es.html.https://your-domain/try/– Conquer compiled to WebAssembly, running entirely in the visitor's browser: a private practice world with its own turns, no account needed; plain static files (seewasm/README.md).https://your-domain/tutorial.html– the first turn of a new nation, key by key, with the real screens, a terminal recording and a narrated video; in Spanish attutorial.es.html, with the Spanish video (seetools/tutorial/README.md).https://your-domain/hall.html– the hall of fame of finished seasons.https://your-domain/game.html– the game page opened by "Play now". It embeds the terminal and adds an on-screen key bar (movement, Esc, Enter, Ctrl-L...) that is shown by default on phones and tablets. On small screens the terminal font is scaled down so the game always gets its 80x24 screen.https://your-domain/play/– the game terminal itself (ttyd). Every player signs in with their own account (see Player accounts below).https://your-domain/status/status.json– public game status, written by the game container at startup and after every turn update.
After signing in, players see a menu instead of a raw game prompt:
- Play – checks that the terminal is at least 80x24, then opens the player's own nation (only the nation password is asked). Quitting the game returns to the menu.
- How to join / how turns work
- Key reference
- Scores
- Full help – the in-game help screens
- My orders for this turn are done (see Early turns when everyone is done)
- Practice world – a private copy of the default world with a ready
nation (
trainee, passwordtrain1) for every account. Players try anything, run its turns themselves and start again at will; it never touches the real game and never blocks its turn updates. Practice worlds live indata/practice/.
The Coach button above the game (or game.html?coach) opens a
step-by-step guide next to the terminal: the same first turn as the
tutorial page (/tutorial.html), with buttons that press the keys for the
player. It follows the game on its own, moving to the next step when the
screen changes.
The menu also shows the last turn update and the turn schedule.
Languages. The site, the guide, the tutorial and its video, the game
page, the coach, the player menu and the invite-code wizard are in English
and Spanish. Every text is in one catalog per language, with English shown
for anything a language does not have yet: web/i18n/ (pages, coach),
tools/tutorial/i18n/ (tutorial and coach steps), conquer/i18n/ (menu and
join wizard). Pages follow ?lang=, the language menu of the home page or
the browser; the game page passes the language to the menu (ttyd
--url-arg: /play/?arg=es, where the menu accepts only a language it has
texts for). web/i18n/README.md explains how to add a language, and
tools/check-i18n.py (run by CI) finds missing or broken texts. The game's
own screens are the original English ones. A daily or weekly
TURN_SCHEDULE is shown in each visitor's language and local time; a
TURN_SCHEDULE_LABEL, if set, replaces it as written, so pick its language.
During the test phase, nations are created by the administrator, not by players:
./add-nation.sh # runs "conqrun -a" inside the running containerAfter the nation, the script asks for its name and creates the player's web account linked to it (see below). Give the player that account and the nation password.
The game asks each nation for its own password; god's is the game administrator's. To set one without knowing the old one (a forgotten god password, a player who lost theirs):
./set-nation-password.sh god
./set-nation-password.sh saharaConquer keeps at most 7 characters of a password (god needs at least 4). The
new password works at once. God's password is also asked by add-nation.sh
once the game is past turn 5.
God's password opens every nation, and the game keeps only a short DES
hash of it, which anyone could guess back from a published world. So the
worlds in this repository (conquer/lib, conquer/practice-world) ship
with god locked: no password opens it until the server sets its own.
deploy-to-vps.sh and new-season.sh ask for it, update-vps.sh and the
container log warn while it is missing, and
./set-nation-password.sh god --if-locked asks only if none is set yet.
Players sign in to /play/ with their own account. Apache checks it and
passes the account name to the game menu, which opens only the nation
assigned to that account (the player just types the nation password). An
account without an assignment opens the nation with the same name, if any.
Administrator accounts may open any nation, god included.
./manage-players.sh add kestrel --nation Elves # create or reset (asks for a
# password, or generates one)
./manage-players.sh add admin2 --admin # may open any nation
./manage-players.sh assign kestrel Dwarves # change the nation
./manage-players.sh remove kestrel # revoke access
./manage-players.sh list # accounts and their nationsPlayers create their own account with an invite code; nobody without one can create an account or reach the game terminal. The administrator hands out the codes:
sudo ./manage-players.sh invite 5 # five single-use codes
sudo ./manage-players.sh invites # the codes not used yet- The player opens Create your account on the home page (
signup.html, orsignup.html?code=K7QM-3XPAto fill the code in), types the code and chooses an account name and a password.conquer-gate, a small service in the game container, checks the code, creates the account and uses the code up; it slows down anyone who keeps trying wrong codes. - The player presses Play now and signs in with that account. The game has them found their nation with its nation builder, links it to the account, and from then on opens it directly.
The page shows ADMIN_CONTACT to visitors without a code. After turn 5 the
game only lets the administrator add nations, so sign-up then asks players
to write to them. SIGNUP=off in the environment file closes it.
sudo ./manage-players.sh list shows new accounts that have not founded
their nation yet with +.
Assignments are stored with the world, in data/lib/.players
(account:nation, * for administrators), so backups and restores keep them.
Worlds without that file (deployments older than this feature) keep the old
behaviour where any account can type any nation name: assign the existing
accounts, starting with ./manage-players.sh assign ADMIN --admin.
The accounts live in data/auth/htpasswd locally and in
/etc/apache2/conquer-web.htpasswd on the VPS (use sudo there); changes
apply immediately. TTYD_USERNAME/TTYD_PASSWORD from the environment file
create the administrator's account the first time (start-local.sh,
deploy-to-vps.sh) as an administrator account, which can open any nation
with its nation password. Failed sign-ins are counted by the fail2ban filter
from setup-security.sh.
ttyd trusts the account name Apache sends, so only Apache may reach it: port
7681 is not published in the local setup and is bound to 127.0.0.1 on the
VPS (anyone with a shell on the VPS could reach it directly).
Turns are resolved automatically by cron inside the game container. Configure
the schedule in config/local.env or config/production.env:
TZ=Europe/Madrid
TURN_SCHEDULE="0 20 * * 0" # weekly, Sundays at 20:00
# TURN_SCHEDULE_LABEL="..." # optional: your own words instead
# of the worked-out schedule
# TURN_SCHEDULE="0 20 * * *" # daily at 20:00
# TURN_SCHEDULE=off # manual updates onlyEvery turn update:
- Makes room for the update. Conquer refuses to update while anyone is in
the game, so the menu stops accepting new games, players still in the game
get a warning on screen every minute and, after
TURN_GRACE_MINUTES(default 5), their session is ended. The game saves their orders when it is stopped this way, and the menu tells them they were disconnected. - Backs up the world to
data/backups/, keeping the lastTURN_BACKUPSarchives (default 10,0disables). Restore one with./restore-world.sh world_turnN_YYYYMMDD_HHMMSS.tar.gz. - Runs the update, retrying every
TURN_RETRY_MINUTES(default 10) up toTURN_MAX_RETRIEStimes (default 18) if it still cannot run. - Reports the result: the landing page and the player menu show a notice
if the last update failed, and
TURN_WEBHOOK_URL(optional) receives a message after every update. The payload has bothtext(Slack, Mattermost) andcontent(Discord) fields.
Details appear in the container logs (./logs.sh).
When a player quits the game, the menu asks whether their orders for the turn
are done (option 6 changes it later). The menu banner and the landing page
show how many nations are done. Once every nation assigned to a player
account (see Player accounts) is done, the turn update starts right away
instead of waiting for the schedule, which remains the deadline for the
slower players. Set TURN_EARLY=off to only count the nations without
running early updates. Marks from an earlier turn never count.
To run a turn immediately:
./run-turn.shAfter changing the schedule, recreate the container so it picks up the new
settings: ./rebuild.sh --quick (it loads the environment file).
The live world is stored on the host in data/lib/ and mounted into the
container, so rebuilding or upgrading the image keeps player progress. On first
start the container copies the default world shipped in the image (conquer/lib/)
into data/lib/.
The game runs as an unprivileged user (conquer, uid CONQUER_UID,
default 1000): the entrypoint only uses root to prepare the volumes and cron,
then ttyd, the menu, the game and every turn update run as that user. data/
is owned by that uid on the host; set CONQUER_UID to your own uid (id -u)
so the backup scripts can read it without sudo.
Every player and the administrator share that user. Because Conquer ties
nations to Unix users, the container makes it the owner of the god nation on
startup (conqowner), otherwise add-nation.sh could only ever create one
nation.
TTYD_USERNAME/TTYD_PASSWORD define the administrator's web account, created
on first start; players get their own accounts with ./manage-players.sh.
Configure them during setup or by editing environment files:
Local Development (config/local.env):
TTYD_USERNAME=your_dev_username
TTYD_PASSWORD=your_dev_password
MAX_CLIENTS=10
SESSION_TIMEOUT=3600VPS Production (config/production.env):
TTYD_USERNAME=your_username
TTYD_PASSWORD=your_strong_password
MAX_CLIENTS=5
SESSION_TIMEOUT=1800🔐 IMPORTANT: Change default credentials before first use!
- Change a password with
./manage-players.sh add NAME(it resets an existing account); no restart is needed. - Other settings: edit the environment file (see above) and restart:
- Local:
./rebuild.sh --quick - VPS:
sudo systemctl restart conquer-web
- Local:
- ✅ Use strong passwords - At least 12 characters with mixed case, numbers, symbols
- ✅ Unique usernames - Don't use common names like 'admin', 'user', 'conquer'
- ✅ Change defaults - Never use 'changeme' or default passwords in production
- ✅ Regular updates - Change credentials periodically
Conquer requires world data to run. Generate it before first use:
# Generate world data (becomes the default world in conquer/lib/)
./generate-world.sh
# Backup the live world (data/lib/)
./backup-world.sh
# Restore the live world from a backup, then restart the container
./restore-world.sh world_backup_YYYYMMDD_HHMMSS.tar.gzgenerate-world.sh replaces the default world shipped in the image. The running
game keeps using data/lib/ until you back it up, remove it and run
./rebuild.sh --force.
Map size: Conquer stores army and capital coordinates in 8 bits, so worlds must be at most 256x256. Larger maps misplace armies into the sea and players see an empty map; the container logs a warning if it detects one.
The default world is made for about 15 players: 112x112 sectors with 60%
water (about 4,400 land sectors), 7 computer nations (anorian, darboth,
edland, fung, goldor, woooo, sahara, listed in conquer/lib/nations) and the
monsters (pirates, nomads, savages, lizards). Conquer holds at most 35
nations counting god, the computer nations and the 4 monsters, so this world
leaves 23 places for players: 15 and room for late arrivals. Neighbours
meet within a few turns and have land to grow for about 25 turns. Every
computer nation in nations takes a player's place: keep the list short
when you generate a world for many players.
conquer-web/
├── conquer/ # Conquer game Docker container
│ ├── lib/ # Default world shipped in the image
│ ├── scripts/ # Entrypoint, player menu, turn runner, status
│ └── tools/ # conqowner (god nation owner fix)
├── web/ # Landing page and mobile-friendly game page
├── data/lib/ # Live world data (created at runtime, not in git)
├── data/public/ # Published status.json (created at runtime)
├── data/auth/ # Player accounts, htpasswd (local setup)
├── apache/ # Apache Docker container (local only)
├── vps/ # VPS-specific configurations
├── config/ # Environment configurations
├── docker-compose.local.yml # Local development (Apache + Conquer)
├── docker-compose.vps.yml # VPS production setup
├── setup-environment.sh # Interactive setup script
├── deploy-to-vps.sh # VPS deployment script
├── generate-world.sh # World data generation
├── rebuild.sh # Rebuild containers (--force, --quick options)
├── logs.sh # View container logs
├── stop.sh # Stop running containers
├── setup-security.sh # Security hardening (fail2ban, rate limiting)
├── check-security.sh # Security status monitoring
├── backup-world.sh # Backup world data
├── restore-world.sh # Restore world from backup
├── reset-to-default-world.sh # Reset to default world
├── health-check.sh # Container health verification
├── add-nation.sh # Create a player nation (admin)
├── manage-players.sh # Player web accounts (admin)
├── run-turn.sh # Run a turn update now (admin)
└── setup-local-certs.sh # Self-signed certificate for local use
# Start services (loads config/local.env)
./start-local.sh
# View logs
./logs.sh
# Rebuild containers (with cache)
./rebuild.sh
# Rebuild containers (force, no cache)
./rebuild.sh --force
# Quick restart (config changes only)
./rebuild.sh --quick
# Check status
./health-check.sh
# Stop services
./stop.sh# Check service status
sudo systemctl status conquer-web
# View logs
sudo journalctl -u conquer-web -f
# Restart service
sudo systemctl restart conquer-web
# Stop service
sudo systemctl stop conquer-web- Authentication: Username/password protection with brute force protection
- Rate Limiting: fail2ban and configurable concurrent user limits
- Session Management: Automatic session timeouts
- SSL/TLS: HTTPS encryption (Let's Encrypt for VPS)
- Container Isolation: Game runs in isolated Docker container
- Security Headers: HSTS, CSP, and comprehensive security headers
- Attack Protection: Bot blocking, request filtering, DoS protection
- Monitoring: Comprehensive logging and security alerts
After basic deployment, enhance security with:
# Run security hardening script
sudo ./setup-security.sh
# Check security status
./check-security.shSee SECURITY.md for detailed security configuration.
- Install Dependencies: Docker, Docker Compose
- Generate World:
./generate-world.sh - Setup Environment:
./setup-environment.sh(choose option 1) - Start Development:
./start-local.sh - Access Game: https://conquer.local
- Prepare VPS: Debian/Ubuntu with Apache installed
- Clone Project:
git clone https://github.com/vejeta/conquer-web.git - Setup Environment:
./setup-environment.sh(choose option 2) - Deploy:
sudo ./deploy-to-vps.sh - Configure DNS: Point domain to VPS IP
- VPS Deployment Guide - Detailed VPS setup instructions
- Security Hardening Guide - Advanced security configuration
- World Management - World data generation and backup
- License Information - GPL v3+ licensing details
# Check container logs
docker logs conquer-local # Local
docker logs conquer-vps # VPS
# Rebuild containers (loads the environment file)
./rebuild.sh --force# Check systemd service
sudo systemctl status conquer-web
# Check detailed logs
sudo journalctl -u conquer-web -n 50
# Test Apache configuration
sudo apache2ctl configtest# Check certificate status
sudo certbot certificates
# Test the automatic renewal
sudo certbot renew --dry-runThis project is licensed under the GPL v3 or later. See LICENSE.md for details.
- Fork the repository
- Create a feature branch
- Make your changes
- Test locally
- Submit a pull request
- Conquer Game: Original strategy game implementation
- ttyd: Terminal over HTTP technology
- Docker: Containerization platform
- Let's Encrypt: Free SSL certificates


