How to Deploy a WASM Game + API
Static WASM game and Python API in a single app. Nginx serves the game and proxies /api to Flask.
Repo structure
my-game/
├── Dockerfile
├── nginx.conf
├── game/ # WASM game (static files)
│ ├── index.html
│ ├── game.wasm
│ ├── game.js
│ └── assets/
├── api/ # Python API (leaderboard, saves)
│ ├── requirements.txt
│ └── app.py
└── start.sh
Dockerfile
Nginx serves static files from /game and reverse-proxies /api to gunicorn running Flask.
FROM python:3.12-slim
RUN apt-get update && apt-get install -y nginx gettext-base && rm -rf /var/lib/apt/lists/* \
&& rm -f /etc/nginx/sites-enabled/default
# Install Python dependencies
WORKDIR /app
COPY api/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY api/ ./api/
# Copy game static files
COPY game/ /usr/share/nginx/html/
# Copy nginx config template and start script
COPY nginx.conf /etc/nginx/conf.d/default.conf.template
COPY start.sh /start.sh
RUN chmod +x /start.sh
EXPOSE $PORT
ENV PORT=8080
CMD ["/start.sh"]
nginx.conf
Serves the game at / and proxies /api to gunicorn on port 5000.
server {
listen ${PORT};
# Serve WASM game
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# Proxy API requests to gunicorn
location /api/ {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
start.sh
Starts gunicorn in the background, then nginx in the foreground.
#!/bin/sh
# Start Flask API
cd /app && gunicorn --bind 127.0.0.1:5000 api.app:app &
# Render nginx config with $PORT and start nginx
envsubst '${PORT}' \
< /etc/nginx/conf.d/default.conf.template \
> /etc/nginx/conf.d/default.conf
nginx -g 'daemon off;'
Example API (api/app.py)
from flask import Flask, jsonify, request
app = Flask(__name__)
scores = []
@app.route("/api/scores", methods=["GET"])
def get_scores():
return jsonify(sorted(scores, reverse=True)[:10])
@app.route("/api/scores", methods=["POST"])
def submit_score():
data = request.get_json()
scores.append(data["score"])
return jsonify({"rank": sorted(scores, reverse=True).index(data["score"]) + 1})
Deploy
Step 1: Create the app
create_app(name: "mygame", github_repo: "github.com/me/my-game")
Step 2: Attach Postgres (for leaderboard, saves)
attach_postgres(app_name: "mygame", environment: "dev", tier: "pg-1")
Step 3: Deploy
deploy_app(app_name: "mygame", branch: "main")
Your game is live at https://mygame-<team>-dev.promptship.dev with the API at /api/.
How does the game talk to the API?
Since both are served from the same domain, use relative URLs:
async function submitScore(score) {
const res = await fetch("/api/scores", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({score})
});
return res.json();
}
No CORS needed: Since the game and API share the same domain, there are no cross-origin issues.
