Deploy a complete browser-based game arcade on AWS with classic games like 2048, PvP Arena, Pac-Man, Super Mario Bros, and QuakeJS!
20260109_042720000_iOS.MP4
- ๐ฏ Multi-Game Arcade - Beautiful game selection screen with multiple browser games
- ๐ Browser-Based - All games run directly in your browser, no downloads needed
- ๐ One-Command Deploy - Full infrastructure and game deployment with
make deploy - ๐ฐ Cost-Optimized - ~$11/month with optional features disabled
- ๐ Secure - SSH restricted, encrypted storage, audit logging
- ๐ Monitoring - Cost budgets, anomaly detection, billing alarms
- ๐จ Modern UI - Responsive game selection interface
- ๐ณ Docker-Based - QuakeJS runs in Docker for reliability
| Game | Type | Players | Description |
|---|---|---|---|
| 2048 | Puzzle | Single | Classic number merging puzzle game |
| PvP Arena | Action | 1-4 | Fast-paced multiplayer arena shooter |
| Pac-Man | Arcade | Single | Classic maze game with ghosts and dots |
| Super Mario Bros | Platformer | Single | HTML5 remake with all 32 original levels |
| Snake | Arcade | Single | Classic snake game - grow longer by eating food |
| Tetris | Puzzle | Single | Legendary puzzle game with falling blocks |
| Pong | Arcade | Single | The original arcade classic |
| Flappy Bird | Arcade | Single | Navigate through pipes in this addictive game |
| Space Invaders | Arcade | Single | Defend Earth from alien invaders |
| Breakout | Arcade | Single | Break all the bricks with your paddle |
| Asteroids | Arcade | Single | Destroy asteroids in space |
| QuakeJS | FPS | Multiplayer | Classic Quake/OpenArena in your browser - no downloads required |
| Tic-Tac-Toe | Puzzle | 2 Players | Classic strategy game - get three in a row |
- AWS Account with billing enabled
- Terraform >= 1.5.0 (Install)
- Ansible >= 2.9 (Install)
- AWS CLI configured (Setup)
- Git for cloning the repository
- QuakeJS Files (see QuakeJS Setup below)
git clone https://github.com/yourusername/openarena-aws.git
cd openarena-awsCreate .env file:
cp .env.example .env
# Edit .env with your valuesKey variables:
# AWS Configuration
AWS_REGION="us-west-2"
INSTANCE_TYPE="t2.micro"
# SSH Configuration
SSH_KEY_NAME="your-key-name"
SSH_PRIVATE_KEY_FILE="./terraform.pem"
SSH_ALLOWED_CIDR="YOUR_IP/32" # Get with: curl ifconfig.me
# Cloudflare (optional - set to "dummy" if not using)
CLOUDFLARE_API_TOKEN="your-token-or-dummy"
CLOUDFLARE_ZONE_ID="your-zone-id"
CLOUDFLARE_ZONE_NAME="example.com"
CLOUDFLARE_SUBDOMAIN="games"
CLOUDFLARE_TTL="300"
# Cost Monitoring
LOG_BUCKET_NAME="your-audit-logs-YOUR_ACCOUNT_ID"
FLOWLOG_BUCKET_NAME="your-flowlogs-YOUR_ACCOUNT_ID"
CUR_BUCKET_NAME="your-cur-YOUR_ACCOUNT_ID"
BILLING_ALERT_EMAIL="your-email@example.com"
MONTHLY_BUDGET_USD="15"Get your AWS Account ID:
aws sts get-caller-identity --query Account --output text
# Update bucket names in .env with your account IDCreate terraform/terraform.tfvars:
cd terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your valuesCritical variables:
aws_region = "us-west-2"
ssh_key_name = "your-key-name"
ssh_allowed_cidr = "YOUR_IP/32"
# S3 Bucket Names (MUST be globally unique - include your AWS Account ID)
log_bucket_name = "your-audit-logs-YOUR_ACCOUNT_ID"
flowlog_bucket_name = "your-flowlogs-YOUR_ACCOUNT_ID"
cur_bucket_name = "your-cur-YOUR_ACCOUNT_ID"
# Email for cost alerts (MUST CONFIRM SNS SUBSCRIPTION!)
billing_alert_email = "your-email@example.com"# Attach required IAM policies
./scripts/attach-iam-policy.shThis creates and attaches:
OpenArenaTerraformMonitoring- SNS, Budgets, Cost Explorer, CUROpenArenaTerraformSecurity- IAM, KMS, GuardDutyOpenArenaTerraformCloudTrail- CloudTrail management
Also attach via AWS Console:
AmazonEC2FullAccessAmazonS3FullAccessAmazonVPCFullAccess
# One-command deployment
make deploy
# OR step-by-step with confirmations
make layered-deployDeployment takes ~8-10 minutes and creates:
- EC2 instance (t2.micro)
- Elastic IP
- Security groups (SSH + HTTP)
- S3 buckets for logging
- CloudTrail audit logging
- Cost monitoring (budgets, alarms)
- All browser games deployed
- QuakeJS Docker container
Before deploying, copy the required QuakeJS files to ansible/files/:
Option 1: Use the setup script (if you have QuakeFiles directory)
# If QuakeFiles is in ../QuakeFiles (relative to repo root)
./scripts/setup-quakejs-files.sh
# Or specify custom path
QUAKEFILES_DIR=/path/to/QuakeFiles ./scripts/setup-quakejs-files.shOption 2: Manual copy
# Copy pak0.pk3 (REQUIRED - ~450MB)
cp /path/to/QuakeFiles/pak0.pk3 ansible/files/pak0.pk3
# Copy Docker image tar (OPTIONAL but recommended - ~1GB)
cp /path/to/QuakeFiles/quakejs_images.tar ansible/files/quakejs_images.tarNote:
pak0.pk3is required for QuakeJS to workquakejs_images.taris optional - if not provided, deployment will attempt to pull from Docker Hub (may fail)
See ansible/files/README.md for more details.
After deployment, visit:
- Game Selection:
http://games.alexflux.com(or your IP) - QuakeJS:
http://games.alexflux.com/quakejs/ - 2048:
http://games.alexflux.com/2048/ - PvP Arena:
http://games.alexflux.com/pvp/ - Pac-Man:
http://games.alexflux.com/pacman/ - Super Mario Bros:
http://games.alexflux.com/mario/
openarena-aws/
โโโ terraform/ # Infrastructure as Code
โ โโโ modules/
โ โ โโโ openarena/ # EC2, EIP, Security Groups
โ โ โโโ cost/ # Monitoring, budgets, CloudTrail
โ โโโ main.tf
โ โโโ terraform.tfvars # Your config (gitignored)
โโโ ansible/ # Configuration management
โ โโโ playbooks/
โ โ โโโ site.yml # Main playbook
โ โโโ roles/
โ โ โโโ web-game/ # Browser games deployment
โ โ โโโ tasks/
โ โ โ โโโ deploy-2048.yml
โ โ โ โโโ deploy-pvp.yml
โ โ โ โโโ deploy-pacman.yml
โ โ โ โโโ deploy-mario.yml
โ โ โ โโโ docker-quakejs.yml
โ โ โโโ templates/
โ โ โโโ game-selection.html.j2
โ โ โโโ nginx.conf.j2
โ โโโ inventory/
โ โโโ hosts.ini # Generated by deploy script
โโโ scripts/ # Deployment automation
โ โโโ deploy.sh # Full deployment
โ โโโ layered-deploy.sh # Step-by-step
โ โโโ destroy.sh # Teardown
โโโ ansible/files/ # Game files (pak0.pk3, quakejs_images.tar)
โ โโโ README.md # Instructions for required files
โโโ Makefile # Convenient commands
โโโ README.md # This file
| Command | Description |
|---|---|
make deploy |
Full deployment (Terraform + Ansible) |
make layered-deploy |
Step-by-step with confirmations |
make dry-run |
Validate without deploying |
make redeploy |
Tear down and rebuild |
make destroy |
Remove all infrastructure |
make validate |
Comprehensive validation |
make security-scan |
Security-focused scan |
make quick-check |
Fast syntax validation |
| Service | Cost | Notes |
|---|---|---|
| EC2 t2.micro | $8.50 | Free tier eligible (750 hrs/month) |
| EBS Storage (8 GB) | $0.80 | GP2 volume |
| Data Transfer | $0.90 | Outbound traffic |
| CloudTrail | FREE | First trail is free |
| SNS | FREE | First 1,000 emails free |
| AWS Budgets | FREE | First 2 budgets free |
| Total | ~$11/month |
| Feature | Cost | Enable in terraform.tfvars |
|---|---|---|
| GuardDuty | $10/month | enable_guardduty = true |
| VPC Flow Logs | $3/month | enable_vpc_flow_logs = true |
| CloudWatch Logs | $1.50/month | enable_cloudwatch_logs = true |
Total with all features: ~$25/month
-
Stop server when not playing:
aws ec2 stop-instances --instance-ids $(terraform output -raw instance_id) # Saves ~$4-7/month if stopped 50% of the time
-
Use free tier: EC2 t2.micro is free tier eligible (750 hours/month)
-
Disable optional features: GuardDuty, VPC Flow Logs disabled by default
- โ
SSH Access Restricted - Only your IP can SSH (
/32CIDR) - โ Encrypted Storage - All S3 buckets encrypted (AES-256)
- โ Audit Logging - CloudTrail enabled (FREE)
- โ Cost Monitoring - Budgets, anomaly detection, billing alarms
- โ
No Hardcoded Secrets - All credentials in
.env(gitignored) - โ Minimal IAM Permissions - Granular policies per service
- โ HTTP Only - No UDP ports exposed (browser-based games only)
- Update
ssh_allowed_cidrto your actual public IP - Never commit
.env,terraform.tfvars, or.pemfiles - Confirm SNS subscription emails for cost alerts
- Review IAM policies before attaching
- Enable CloudTrail (already enabled by default)
- Use Cloudflare API token with minimal permissions (DNS + Page Rules Edit)
AWS Credentials (in order of preference):
- IAM Roles - Use EC2 Instance Profiles or OIDC for CI/CD
- AWS SSO/CLI Profiles -
aws configure sso - Environment Variables - For CI/CD pipelines
- Local Credentials -
~/.aws/credentials(development only)
Cloudflare API Token:
- Use environment variable:
export CLOUDFLARE_API_TOKEN="your-token" - Set in
.envfile (gitignored) - Never commit tokens to git
*.tfvars(except*.tfvars.example)*.tfstateand*.tfstate.*.terraform/directory.envfiles- SSH keys (
.pem,.key,.pub) - Any files containing secrets
After deployment, you'll receive 2 confirmation emails:
-
Budget Alerts Subscription
- Subject: "AWS Notification - Subscription Confirmation"
- Sender:
no-reply@sns.amazonaws.com - Click "Confirm subscription" link
-
Anomaly Alerts Subscription
- Another email with same subject
- Click "Confirm subscription" link
Until you confirm, you won't receive any cost alerts!
This is a manual AWS Console step:
- Sign in to AWS Console
- Go to: Billing โ Billing Preferences
- Check: "Receive CloudWatch Billing Alerts"
- Click: Save preferences
- Wait 15-30 minutes for billing metrics to appear
# Get server details
cd terraform
terraform output
# Output includes:
# - public_ip: EC2 instance IP address
# - fqdn: DNS name (if Cloudflare configured)
# - ssh_user: SSH username (ec2-user)Test SSH connection:
ssh -i terraform.pem ec2-user@$(terraform output -raw public_ip)
# Check web server status
sudo systemctl status nginx
# Check QuakeJS container status
sudo docker ps
sudo docker logs quakejs
# View web server logs
sudo journalctl -u nginx -fTest games:
- Open browser and go to:
http://<public_ip>orhttp://games.yourdomain.com - Select a game from the menu (e.g., QuakeJS, 2048, PvP Arena)
- Play directly in your browser!
Option 1: Direct IP
URL: http://<public_ip>
Example: http://54.123.45.67
Option 2: DNS (if Cloudflare configured)
URL: http://games.example.com
QuakeJS: http://games.example.com/quakejs/
SSH into instance:
ssh -i ~/.ssh/id_rsa ec2-user@<public_ip>Web server management:
# Check web server status
sudo systemctl status nginx
# View web server logs
sudo journalctl -u nginx -f
# Restart web server
sudo systemctl restart nginxQuakeJS container management:
# Check container status
sudo docker ps
sudo docker logs quakejs
# Restart QuakeJS container
cd /opt/quakejs
sudo docker-compose restart
# Stop QuakeJS container
sudo docker-compose down
# Start QuakeJS container
sudo docker-compose up -dAWS Cost Explorer (Console):
- Go to: Billing โ Cost Explorer
- View: Daily/monthly costs by service
- Filter by: Tags (Project=openarena)
AWS Budgets (Console):
- Go to: Billing โ Budgets
- View: "openarena-monthly-total" budget
- See: Current spend vs budget
Cost Anomaly Detection (Console):
- Go to: Cost Management โ Cost Anomaly Detection
- View: Detected anomalies and root causes
CloudWatch Billing Alarm (Console):
- Go to: CloudWatch (us-east-1) โ Alarms
- View: "openarena-estimated-charges" alarm status
Cost via CLI:
# Get current month spend
aws ce get-cost-and-usage \
--time-period Start=$(date -u +%Y-%m-01),End=$(date -u +%Y-%m-%d) \
--granularity MONTHLY \
--metrics UnblendedCost \
--group-by Type=DIMENSION,Key=SERVICEBudget Alerts (3 thresholds):
- 50% of budget: Early warning ($7.50 if $15 budget)
- 80% of budget: Critical warning ($12 if $15 budget)
- 100% of budget: Budget exceeded ($15)
- 100% forecasted: AWS predicts you'll exceed budget by month-end
Cost Anomaly Alerts:
- Daily digest: Summary of detected spending anomalies
- Impact threshold: Only alerts if anomaly >= $5 (configurable)
- Example: "EC2 cost increased 200% on Jan 15"
CloudWatch Billing Alarm:
- Failsafe backup: Triggers if estimated charges exceed $20 (configurable)
- Frequency: Checks every 6 hours
Want to add more games? It's easy!
Games must be:
- โ Pure HTML5/CSS/JavaScript - No server-side code required
- โ Available on GitHub - As a zip download or git repository
- โ No build process - Or pre-built files available
- โ Browser-compatible - Works in modern browsers (Chrome, Firefox, Safari, Edge)
- โ Open source - With a permissive license
Create ansible/roles/web-game/tasks/deploy-[gamename].yml:
---
# Deploy [Game Name] to /[gamename]/ subdirectory
- name: Create [Game Name] game directory
ansible.builtin.file:
path: "{{ web_game_dir }}/[gamename]"
state: directory
mode: "0755"
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
- name: Download [Game Name] game
ansible.builtin.get_url:
url: https://github.com/user/repo/archive/refs/heads/master.zip
dest: /tmp/[gamename]-master.zip
mode: "0644"
register: game_download
- name: Extract [Game Name] game
ansible.builtin.unarchive:
src: /tmp/[gamename]-master.zip
dest: /tmp
remote_src: true
creates: /tmp/[gamename]-master
- name: Copy [Game Name] game files
ansible.builtin.shell: |
if [ -d /tmp/[gamename]-master ]; then
find /tmp/[gamename]-master -mindepth 1 -maxdepth 1 -exec cp -r {} {{ web_game_dir }}/[gamename]/ \;
chown -R {{ nginx_user }}:{{ nginx_user }} {{ web_game_dir }}/[gamename]/
fi
changed_when: trueEdit ansible/roles/web-game/tasks/main.yml:
- name: Deploy [Game Name] game
ansible.builtin.include_tasks: deploy-[gamename].ymlEdit ansible/roles/web-game/templates/game-selection.html.j2:
<a href="/[gamename]/" class="game-card">
<span class="game-icon">๐ฎ</span>
<h2 class="game-title">[Game Name]</h2>
<span class="badge singleplayer">Single Player</span>
<span class="badge action">Action</span>
<p class="game-description">
[Game description]
</p>
<ul class="game-features">
<li>Feature 1</li>
<li>Feature 2</li>
</ul>
<span class="play-btn">Play Now โ</span>
</a>Badge options:
singleplayer- Blue badgemultiplayer- Green badgepuzzle- Orange badgeaction- Red badgearcade- Purple badgeplatformer- Pink badge
Edit ansible/roles/web-game/templates/nginx.conf.j2:
location /[gamename]/ {
alias {{ web_game_dir }}/[gamename]/;
try_files $uri $uri/ /[gamename]/index.html;
}make deploy1. Terraform init fails - Cloudflare provider error
- Solution: Create
.envwithCLOUDFLARE_API_TOKEN="dummy"even if not using Cloudflare.
2. Ansible fails - Python version error
- Solution: Already fixed! Python 3.8 is auto-installed via EC2 user_data.
3. SSH connection refused
- Solution:
- Verify SSH key path in
.env - Check key permissions:
chmod 400 terraform.pem - Ensure
ssh_allowed_cidrmatches your IP - Wait 60 seconds after instance creation for SSH to be ready
- Verify SSH key path in
4. Games not loading
- Solution:
- Check Nginx is running:
sudo systemctl status nginx - Verify files exist:
ls -la /var/www/html/[gamename]/ - Check Nginx logs:
sudo tail -f /var/log/nginx/error.log - Test locally:
curl http://localhost/[gamename]/
- Check Nginx is running:
5. QuakeJS not working
- Solution:
- Check Docker container:
sudo docker ps - View container logs:
sudo docker logs quakejs - Verify pak0.pk3 exists:
ls -lh /opt/quakejs/baseoa/pak0.pk3 - Restart container:
cd /opt/quakejs && sudo docker-compose restart
- Check Docker container:
6. Terraform apply fails with "AccessDenied"
- Solution:
# Verify AWS credentials aws sts get-caller-identity # Check IAM permissions (need Admin or Power User) aws iam get-user --user-name <your-username>
7. "Bucket name already exists" error
- Solution: S3 bucket names are globally unique. Change bucket names in
.envto include your account ID.
8. Not receiving budget alert emails
- Solutions:
- Check SNS subscription confirmation:
aws sns list-subscriptions # Look for Status="PendingConfirmation" - Check email spam folder
- Verify budget exists:
aws budgets describe-budgets --account-id <account-id>
- Check SNS subscription confirmation:
9. CloudWatch Billing Alarm stuck in "INSUFFICIENT_DATA"
- Solutions:
- Enable Billing Alerts (one-time): AWS Console โ Billing โ Preferences โ Check "Receive CloudWatch Billing Alerts"
- Wait 15-30 minutes for metric to appear
- Verify in us-east-1 region
# Check Terraform state
cd terraform && terraform show
# SSH to server
ssh -i terraform.pem ec2-user@$(terraform output -raw public_ip)
# Check game server status
sudo systemctl status nginx
sudo docker ps # Check QuakeJS container
# View logs
sudo journalctl -u nginx -f
sudo docker logs quakejs # View QuakeJS container logs
# Test game URLs
curl http://localhost/2048/
curl http://localhost/pvp/
curl http://localhost/quakejs/ # Test QuakeJSWe welcome contributions! Here's how to get started:
git clone https://github.com/yourusername/openarena-aws.git
cd openarena-awsgit checkout -b feature/add-new-game- Add new games (see Adding New Games)
- Fix bugs
- Improve documentation
- Add features
# Validate syntax
make quick-check
# Comprehensive validation
make validate
# Security scan
make security-scan- Write a clear description of your changes
- Reference any related issues
- Ensure all tests pass
- Update documentation if needed
- Code Style: Follow existing Ansible/Terraform conventions
- Documentation: Update README.md for new features
- Testing: Test locally before submitting PR
- Commits: Write clear, descriptive commit messages
If you want to use a custom domain (e.g., games.alexflux.com):
-
Create Cloudflare API Token:
- Go to: https://dash.cloudflare.com/profile/api-tokens
- Click "Create Token" โ "Create Custom Token"
- Permissions needed:
- Zone โ DNS โ Edit
- Zone โ Page Rules โ Edit
- Zone Resources: Select your zone (
alexflux.com) - Copy the token
-
Add Token to
.env:CLOUDFLARE_API_TOKEN="your-token-here" CLOUDFLARE_ZONE_ID="your-zone-id" CLOUDFLARE_ZONE_NAME="alexflux.com" CLOUDFLARE_SUBDOMAIN="games"
-
Configure SSL Mode:
- Root domain (
alexflux.com): Set to Full/Full Strict in Cloudflare dashboard - Games subdomain (
games.alexflux.com): Automatically set to Flexible via Page Rule - This allows Vercel (root) to use Full Strict while QuakeJS (games) uses Flexible
- Root domain (
The deployment automatically creates a Page Rule:
- Target:
games.alexflux.com/* - SSL Mode: Flexible
- This allows the root domain to use Full/Full Strict while games subdomain uses Flexible
This project is for educational and personal use.
- QuakeJS - Browser-based Quake engine (mazaclub/quakejs)
- OpenArena - Free, open-source Quake III Arena clone (used for game assets)
- 2048 - gabrielecirulli/2048
- PvP - kesiev/pvp
- Pac-Man - GerardAlbajar/Pacman-js
- Super Mario Bros - umaim/Mario
- Terraform - Infrastructure as Code
- Ansible - Configuration management
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Made with โค๏ธ for the gaming community
Status: โ Production Ready | Last Updated: 2025-12-19