Skip to content

Commit ea6f6ef

Browse files
feat(deploy): add Fly.io deployment configs and automation
1 parent 9aafa4d commit ea6f6ef

File tree

9 files changed

+163
-1
lines changed

9 files changed

+163
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ htmlcov/
2121
*.log
2222
runs/*
2323
!runs/.gitkeep
24+
.tmp/
25+
worldmodel_gym.db
2426
paper/*.aux
2527
paper/*.bbl
2628
paper/*.blg

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ VENV ?= .venv
22
PYTHON ?= $(VENV)/bin/python
33
PIP ?= $(VENV)/bin/pip
44

5-
.PHONY: setup test lint demo paper deploy stop
5+
.PHONY: setup test lint demo paper deploy stop deploy-fly
66

77
setup:
88
python3 -m venv $(VENV)
@@ -43,3 +43,6 @@ deploy:
4343

4444
stop:
4545
./scripts/stop_local.sh
46+
47+
deploy-fly:
48+
./scripts/deploy_fly.sh

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,5 @@ make test
5454
make paper
5555
make deploy
5656
make stop
57+
make deploy-fly
5758
```

docker/web.fly.Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM node:20-alpine
2+
3+
WORKDIR /app
4+
5+
COPY web/package.json web/package-lock.json* /app/
6+
RUN npm ci
7+
8+
COPY web /app
9+
10+
ARG NEXT_PUBLIC_API_BASE
11+
ENV NEXT_PUBLIC_API_BASE=$NEXT_PUBLIC_API_BASE
12+
RUN npm run build
13+
14+
EXPOSE 3000
15+
CMD ["npm", "run", "start", "--", "--hostname", "0.0.0.0", "--port", "3000"]

docs/DEPLOYMENT.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Deployment
2+
3+
## Fly.io
4+
5+
This repo supports one-command Fly deployment for both API and web services.
6+
7+
```bash
8+
make deploy-fly
9+
```
10+
11+
Default app names:
12+
- API: `worldmodel-gym-api-biru`
13+
- Web: `worldmodel-gym-web-biru`
14+
15+
Override names/region if needed:
16+
17+
```bash
18+
API_APP=my-api-name WEB_APP=my-web-name REGION=iad make deploy-fly
19+
```
20+
21+
What the script does:
22+
- creates Fly apps if missing
23+
- creates a persistent volume (`data`) for API sqlite storage
24+
- sets a random `WMG_UPLOAD_TOKEN` secret on API app
25+
- deploys API using `docker/server.Dockerfile`
26+
- deploys web using `docker/web.fly.Dockerfile`
27+
28+
After deploy, it prints:
29+
- API URL
30+
- Web URL
31+
- upload token (store securely)

fly/api.fly.toml.tmpl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
app = "__API_APP__"
2+
primary_region = "iad"
3+
4+
[build]
5+
dockerfile = "docker/server.Dockerfile"
6+
7+
[env]
8+
WMG_DB_URL = "sqlite:////data/worldmodel_gym.db"
9+
WMG_STORAGE_DIR = "/data/storage"
10+
11+
[mounts]
12+
source = "data"
13+
destination = "/data"
14+
15+
[http_service]
16+
internal_port = 8000
17+
force_https = true
18+
auto_start_machines = true
19+
auto_stop_machines = "stop"
20+
min_machines_running = 0
21+
processes = ["app"]
22+
23+
[[vm]]
24+
memory_mb = 1024
25+
cpus = 1
26+
cpu_kind = "shared"

fly/web.fly.toml.tmpl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
app = "__WEB_APP__"
2+
primary_region = "iad"
3+
4+
[build]
5+
dockerfile = "docker/web.fly.Dockerfile"
6+
7+
[build.args]
8+
NEXT_PUBLIC_API_BASE = "__API_BASE__"
9+
10+
[http_service]
11+
internal_port = 3000
12+
force_https = true
13+
auto_start_machines = true
14+
auto_stop_machines = "stop"
15+
min_machines_running = 0
16+
processes = ["app"]
17+
18+
[[vm]]
19+
memory_mb = 1024
20+
cpus = 1
21+
cpu_kind = "shared"

scripts/deploy_fly.sh

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
FLYCTL="${FLYCTL:-$HOME/.fly/bin/flyctl}"
6+
API_APP="${API_APP:-worldmodel-gym-api-biru}"
7+
WEB_APP="${WEB_APP:-worldmodel-gym-web-biru}"
8+
REGION="${REGION:-iad}"
9+
10+
if [[ ! -x "$FLYCTL" ]]; then
11+
echo "flyctl not found at $FLYCTL"
12+
exit 1
13+
fi
14+
15+
cd "$ROOT_DIR"
16+
mkdir -p .tmp/fly
17+
18+
if ! "$FLYCTL" auth whoami >/dev/null 2>&1; then
19+
echo "Not authenticated with Fly. Run: $FLYCTL auth login"
20+
exit 1
21+
fi
22+
23+
ensure_app() {
24+
local app_name="$1"
25+
if ! "$FLYCTL" apps list | awk '{print $1}' | grep -qx "$app_name"; then
26+
"$FLYCTL" apps create "$app_name"
27+
fi
28+
}
29+
30+
ensure_app "$API_APP"
31+
ensure_app "$WEB_APP"
32+
33+
if ! "$FLYCTL" volumes list -a "$API_APP" | awk 'NR>1{print $1}' | grep -qx "data"; then
34+
"$FLYCTL" volumes create data --app "$API_APP" --region "$REGION" --size 1
35+
fi
36+
37+
API_TOKEN="$(openssl rand -hex 24)"
38+
"$FLYCTL" secrets set WMG_UPLOAD_TOKEN="$API_TOKEN" --app "$API_APP"
39+
40+
API_TOML=".tmp/fly/api.fly.toml"
41+
WEB_TOML=".tmp/fly/web.fly.toml"
42+
API_BASE="https://${API_APP}.fly.dev"
43+
44+
sed "s/__API_APP__/${API_APP}/g" fly/api.fly.toml.tmpl > "$API_TOML"
45+
sed -e "s/__WEB_APP__/${WEB_APP}/g" -e "s|__API_BASE__|${API_BASE}|g" fly/web.fly.toml.tmpl > "$WEB_TOML"
46+
47+
"$FLYCTL" deploy --config "$API_TOML" --app "$API_APP" --remote-only
48+
"$FLYCTL" deploy --config "$WEB_TOML" --app "$WEB_APP" --remote-only
49+
50+
echo
51+
52+
echo "Fly deployment complete"
53+
echo "API: https://${API_APP}.fly.dev"
54+
echo "WEB: https://${WEB_APP}.fly.dev"
55+
echo "WMG_UPLOAD_TOKEN: $API_TOKEN"

server/worldmodel_server/main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pathlib import Path
66

77
from fastapi import Depends, FastAPI, File, Header, HTTPException, Query, UploadFile
8+
from fastapi.middleware.cors import CORSMiddleware
89
from fastapi.responses import FileResponse
910
from sqlalchemy import desc, select
1011
from sqlalchemy.orm import Session
@@ -16,6 +17,13 @@
1617
from worldmodel_server.storage import ensure_storage_dirs, load_json, run_dir, save_upload_file
1718

1819
app = FastAPI(title=settings.app_name)
20+
app.add_middleware(
21+
CORSMiddleware,
22+
allow_origins=["*"],
23+
allow_credentials=False,
24+
allow_methods=["*"],
25+
allow_headers=["*"],
26+
)
1927

2028

2129
@app.on_event("startup")

0 commit comments

Comments
 (0)