/deploy
Deploy
Deploy Skillomatic to production using SST.
RELATED: This command is paired with /rollback. Keep deployment logic in sync.
When updating: check /rollback for health endpoints, verification steps
Quick Deploy
Run these commands in sequence. Stop if any fails.
- Check upload speed (Docker image is ~300MB, need decent upload for MCP changes):
dd if=/dev/zero of=/tmp/speedtest.bin bs=1M count=2 2>/dev/null && curl -s -w "%{speed_upload}" -X POST -H "Content-Type: application/octet-stream" --data-binary @/tmp/speedtest.bin "https://speed.cloudflare.com/__up" -o /dev/null | awk '{mbps = $1 * 8 / 1000000; if (mbps < 10) printf "WARNING: Upload speed is %.1f Mbps - Docker push may take 5+ mins. Consider switching networks.\n", mbps; else printf "OK: Upload speed is %.1f Mbps\n", mbps}' - Check git is clean and get hash:
git diff --quiet && git diff --cached --quiet || echo "ERROR: Uncommitted changes" - Check extension zip version matches manifest, rebuild if needed:
grep '"version"' apps/skillomatic-scraper/manifest.json | sed 's/.*"\([0-9]*\.[0-9]*\.[0-9]*\)".*/\1/'unzip -p apps/web/public/skillomatic-scraper.zip manifest.json 2>/dev/null | grep '"version"' | sed 's/.*"\([0-9]*\.[0-9]*\.[0-9]*\)".*/\1/'If versions differ, rebuild the zip:
cd apps/skillomatic-scraper && rm -f ../web/public/skillomatic-scraper.zip && zip -r ../web/public/skillomatic-scraper.zip . -x 'node_modules/*' -x '*.git*' -x '*.DS_Store' && cd ../.. - Check what changed since last deploy:
git tag --list '[0-9]*' --sort=-v:refname | head -1Use the tag number from above:
git diff --name-only <TAG>..HEAD
Analyze the changed files to determine if deployment is needed:
| Changed Path | Affects |
|---|---|
apps/api/ | API (Lambda) |
apps/web/ | Web (CloudFront) |
apps/mcp-server/ | MCP (ECS) |
packages/db/ | API, MCP (both use DB) |
packages/shared/ | API, Web, MCP (all use shared) |
packages/mcp/ | API (MCP tools in API) |
sst.config.ts | All services |
skills/ | None (runtime data) |
If NO changes affect deployable services (e.g., only docs, .claude/, skills/), skip to step 9 and report “No deployment needed.”
- Run typecheck:
pnpm typecheck - Check Turso auth before schema push:
turso auth whoamiIf not logged in, run
turso auth loginfirst. - Push schema to prod (ALWAYS run - schema drift can occur even without local changes):
pnpm db:push:prod
If Drizzle warns about data-loss (e.g., adding NOT NULL column without default), do NOT proceed with truncation. Instead:
- Add the column manually with a default value:
turso db shell skillomatic "ALTER TABLE <table> ADD COLUMN <column> <TYPE> NOT NULL DEFAULT <value>;" - Then re-run
pnpm db:push:prod- it should now show no changes or safe changes only.
Common columns that need manual addition:
- Boolean columns:
INTEGER NOT NULL DEFAULT 0(0=false, 1=true) - Text columns with default:
TEXT NOT NULL DEFAULT '<value>' - Nullable text:
TEXT(no NOT NULL, no DEFAULT needed)
- Bump Docker cache bust if MCP-related changes (packages/mcp/, apps/mcp-server/, packages/db/):
# Check current cache bust version grep 'CACHEBUST=v' apps/mcp-server/DockerfileIf MCP-related files changed, increment the version number (e.g., v6 → v7):
sed -i '' 's/CACHEBUST=v[0-9]*/CACHEBUST=v<NEW_VERSION>/' apps/mcp-server/Dockerfile git add apps/mcp-server/Dockerfile && git commit -m "Bump Docker cache bust" && git push - Deploy with git hash:
SKILLOMATIC_DEPLOY=1 GIT_HASH="$(git rev-parse --short HEAD)" pnpm sst deploy --stage production
Note: Always run a full deploy (no
--targetflag). SST v3’s--targetflag has known issues where Lambda code may not update reliably. The MCP Docker build is cached when unchanged, so full deploys are fast.
- Verify services are responding and git hashes match (call in parallel):
curl -s "https://api.skillomatic.technology/health" | jq -r '.gitHash'curl -s "https://mcp.skillomatic.technology/health" | jq -r '.gitHash'curl -s "https://skillomatic.technology" | grep 'git-hash' | grep -o 'content="[^"]*"' | grep -o '"[^"]*"$' | tr -d '"'
All three hashes must match the local commit. Retry with exponential backoff (2-64s) if CDN hasn’t propagated or MCP hasn’t rolled over (ECS rolling deployment can take up to 2 minutes).
- Create and push incremented version tag:
git tag --list '[0-9]*' --sort=-v:refname | head -1Increment the tag number manually (e.g., if output is
17, use18):git tag <NEW_TAG> && git push origin <NEW_TAG> - Report success with deployed services, git hashes, and the new version tag.
Stops on first failure. Always runs full deploy (MCP Docker is cached when unchanged). Uses exponential backoff (2-64s) for CDN propagation. Uses drizzle-kit push to sync schema to Turso.
Note: Schema changes should deprecate columns before removing them to support rollbacks. See /rollback command.
First-Time Setup
Set secrets as SST secrets (one-time). Note: names must match sst.config.ts:
pnpm sst secret set TursoDatabaseUrl "$(turso db show skillomatic --url)" --stage production
pnpm sst secret set TursoAuthToken "$(turso db tokens create skillomatic)" --stage production
pnpm sst secret set JwtSecret "$(openssl rand -hex 32)" --stage production
pnpm sst secret set GoogleClientId "$GOOGLE_CLIENT_ID" --stage production
pnpm sst secret set GoogleClientSecret "$GOOGLE_CLIENT_SECRET" --stage production
Seed Production Database
After schema push or DB recreation:
TURSO_DATABASE_URL=$(turso db show skillomatic --url) \
TURSO_AUTH_TOKEN=$(turso db tokens create skillomatic) \
pnpm --filter @skillomatic/db seed:prod
Debugging
# API Health
curl -s "https://api.skillomatic.technology/health" | jq .
# MCP Server Health
curl -s "https://mcp.skillomatic.technology/health" | jq .
# Login
curl -s -X POST "https://api.skillomatic.technology/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"superadmin@skillomatic.technology","password":"Skillomatic2024"}' | jq .
# API key auth
curl -s -H "Authorization: Bearer sk_live_prod_super_admin_debug_key_2024" \
"https://api.skillomatic.technology/v1/me" | jq .
Test credentials: superadmin@skillomatic.technology / Skillomatic2024
Troubleshooting
| Error | Fix |
|---|---|
| “Invalid email or password” | Seed prod database (see above) |
| “Failed query” with SQL | Schema out of sync - run pnpm db:push:prod |
| “is not valid JSON” on login | Missing columns in prod DB - check schema with turso db shell skillomatic "PRAGMA table_info(<table>);" and add missing columns manually (see step 5) |
| Lambda using stale secrets | Run pnpm sst secret set TursoAuthToken "$(turso db tokens create skillomatic)" --stage production |
| Recreate database from scratch | turso db destroy skillomatic --yes && turso db create skillomatic, then push schema + seed + update SST secrets |
SST Secrets Note
Secrets are accessed at runtime via Resource import (not env vars). When you run sst secret set, SST automatically restarts Lambda containers to pick up new values. Secret names must match exactly:
TursoDatabaseUrl(not TURSO_DATABASE_URL)TursoAuthToken(not TURSO_AUTH_TOKEN)JwtSecret,NangoSecretKey,NangoPublicKey,GoogleClientId,GoogleClientSecret
Rollback
Rollback to a previous production deployment by tag number.
RELATED: This command is paired with /deploy. Keep deployment logic in sync.
When updating: check /deploy for target mapping, health endpoints, verification steps
Usage
/rollback- Rollback to previous version (one before current production)/rollback <tag>- Rollback to specific tag (e.g.,/rollback 3)
Steps
- Get current production hashes and find what tag is running:
curl -s "https://api.skillomatic.technology/health" | jq -r '.gitHash'curl -s "https://mcp.skillomatic.technology/health" | jq -r '.gitHash'git fetch --tagsCompare the production hash against tags to find which tag is currently deployed.
- Determine target tag:
- If no argument: use the tag before the currently running tag
- If argument provided: use that tag number
- Check if already running target:
- Compare the target tag’s hash against the current production hash
- If they match, report “Production is already running tag X” and stop
- Do not proceed with deployment if already on target version
- Show rollback info and confirm:
git log -1 --format='%h %s' <target_tag>Ask user to confirm: “Rollback from tag X to tag Y? (y/n)”
- Proceed only if user confirms
- If declined, report “Rollback cancelled” and stop
- Checkout target tag:
git checkout <target_tag> - Run typecheck:
pnpm typecheck - Deploy all services (NO db:push - schema stays current):
GIT_HASH=<target_hash> pnpm sst deploy --stage production
Note: Rollback deploys ALL services (no --target flag) because we’re reverting to a known-good state. Skipping db:push:prod intentionally - schema development should deprecate columns before dropping them, ensuring old code can run against newer schemas.
- Verify all services are responding and hashes match (call in parallel):
curl -s "https://api.skillomatic.technology/health" | jq -r '.gitHash'curl -s "https://mcp.skillomatic.technology/health" | jq -r '.gitHash'curl -s "https://skillomatic.technology" | grep 'git-hash' | sed 's/.*content="\([^"]*\)".*/\1/'
All three hashes must match the target tag’s commit hash.
Retry with exponential backoff (2-64s) if CDN hasn’t propagated or MCP hasn’t rolled over (ECS rolling deployment can take up to 2 minutes).
- Return to main:
git checkout main - Report success with rolled-back tag and git hash.
Troubleshooting
If rollback fails mid-deploy, you’re in detached HEAD state. Run git checkout main to return.
Health Endpoints
| Service | Endpoint |
|---|---|
| API | https://api.skillomatic.technology/health |
| MCP | https://mcp.skillomatic.technology/health |
| Web | https://skillomatic.technology (check git-hash meta tag) |