Overview
Beyond a single push → deploy hook, production teams need branch previews, environment promotion, gated releases, and instant rollbacks. Hostwares exposes these via a small API surface that fits any CI system — GitHub Actions, GitLab CI, or plain curl.
| Pattern | Use Case | Mechanism |
|---|---|---|
| Branch previews | PR review without touching prod | Deploy branch to preview site |
| Gated deploys | Require tests / approval | CI job → API deploy after pass |
| Env promotion | Staging → production | Same image, different site/env |
| Instant rollback | Bad release | Rollback to prior image (no rebuild) |
Deployment Model
Hostwares treats each site as an environment. A typical setup:
| Site | Branch | URL | Env Vars |
|---|---|---|---|
my-app-prod | main | app.example.com | Prod DB, prod keys |
my-app-staging | staging | staging.hostwares.app | Staging DB, test keys |
my-app-preview | PR branches | Per-PR subdomain | Preview DB / seeded data |
# Core API — same for any CI
# Trigger deploy for a specific branch
curl -X PATCH https://hostwares.com/api/sites/$SITE_ID \
-H "Authorization: Bearer $HOSTWARES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "deploy", "branch": "staging"}'
# Poll status
curl -s https://hostwares.com/api/sites/$SITE_ID \
-H "Authorization: Bearer $HOSTWARES_API_KEY" | jq .status
# QUEUED | BUILDING | DEPLOYING | RUNNING | FAILEDGitHub Actions
Simple: deploy on push to main
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
run: npm ci && npm test
- name: Deploy to Hostwares
run: |
curl -f -X PATCH "https://hostwares.com/api/sites/${{ secrets.HOSTWARES_SITE_ID }}" \
-H "Authorization: Bearer ${{ secrets.HOSTWARES_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"action": "deploy"}'
- name: Wait for healthy
run: |
for i in {1..30}; do
STATUS=$(curl -s "https://hostwares.com/api/sites/${{ secrets.HOSTWARES_SITE_ID }}" \
-H "Authorization: Bearer ${{ secrets.HOSTWARES_API_KEY }}" | jq -r .status)
echo "Status: $STATUS"
[[ "$STATUS" == "RUNNING" ]] && exit 0
[[ "$STATUS" == "FAILED" ]] && exit 1
sleep 10
done
echo "Timed out waiting for deploy" && exit 1With concurrency control
# Prevent overlapping deploys per branch
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
jobs:
deploy:
# Only deploy if tests passed and branch is main
if: github.ref == 'refs/heads/main'
# ... same steps as aboveBranch Previews
Give every PR its own live URL by deploying the branch to a preview site.
# .github/workflows/preview.yml
name: Preview
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
- name: Deploy preview
run: |
curl -f -X PATCH "https://hostwares.com/api/sites/${{ secrets.PREVIEW_SITE_ID }}" \
-H "Authorization: Bearer ${{ secrets.HOSTWARES_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"action": "deploy", "branch": "${{ github.head_ref }}"}'
- name: Comment PR with URL
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "Preview: https://preview-${{ github.event.number }}.hostwares.app"
});| Preview Strategy | Pros | Cons |
|---|---|---|
| One preview site, redeployed per PR | Simple, cheap | Only one PR preview at a time |
| Site per PR (via API create) | Parallel previews | Requires cleanup job |
| Single site with branch param | No extra sites | Branch must exist on Hostwares remote |
Gated Deploys
Require checks to pass before production deploys — tests, lint, typecheck, or manual approval.
# .github/workflows/gated.yml
name: Gated Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build # fail fast before Hostwares build
deploy:
needs: test
runs-on: ubuntu-latest
environment: production # GitHub environment gate (requires approval if configured)
steps:
- name: Deploy to Hostwares
run: |
curl -f -X PATCH "https://hostwares.com/api/sites/${{ secrets.HOSTWARES_SITE_ID }}" \
-H "Authorization: Bearer ${{ secrets.HOSTWARES_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"action": "deploy"}'Use GitHub's Environments → Required reviewers to add a manual approval step before the deploy job runs.
Multi-Environment
Promote the same artifact from staging to production without rebuilding:
# Promote: redeploy prod site from the already-tested branch/commit
# Option A: deploy prod from main after staging passed
curl -X PATCH https://hostwares.com/api/sites/$PROD_SITE_ID \
-H "Authorization: Bearer $HOSTWARES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "deploy", "branch": "main"}'
# Option B: separate sites per env, same repo
# staging site -> branch staging
# prod site -> branch main
# Merge staging -> main only after QA
# GitLab CI equivalent
deploy_prod:
stage: deploy
only: [main]
script:
- curl -f -X PATCH "https://hostwares.com/api/sites/$PROD_SITE_ID"
-H "Authorization: Bearer $HOSTWARES_API_KEY"
-H "Content-Type: application/json"
-d '{"action": "deploy"}'
environment: production| Environment | Site ID Secret | Branch | Domain |
|---|---|---|---|
| Staging | STAGING_SITE_ID | staging | staging.hostwares.app |
| Production | PROD_SITE_ID | main | app.example.com |
Secrets & Scoping
- Store
HOSTWARES_API_KEYandHOSTWARES_SITE_IDas CI secrets — never in the repo. - Scope env vars per site — staging and prod sites have independent Site → Environment values.
- Mark sensitive values as Encrypted so they are masked in logs and the dashboard.
# Rotate a secret via API
curl -X PATCH https://hostwares.com/api/sites/$SITE_ID \
-H "Authorization: Bearer $HOSTWARES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"envVariables": [
{"key": "JWT_SECRET", "value": "new-value-here", "encrypted": true}
]
}'
# Then redeploy to apply
curl -X PATCH https://hostwares.com/api/sites/$SITE_ID \
-H "Authorization: Bearer $HOSTWARES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "deploy"}'Rollback Automation
Every successful deploy keeps its image. Roll back without rebuilding:
# Manual rollback
curl -X PATCH https://hostwares.com/api/sites/$SITE_ID \
-H "Authorization: Bearer $HOSTWARES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "rollback", "deploymentId": "dep_prev123"}'
# Auto-rollback on health check failure (in CI)
HEALTH_URL="https://app.example.com/api/health"
if ! curl -f -s "$HEALTH_URL" | jq -e '.ok == true' > /dev/null; then
echo "Health check failed — rolling back"
curl -X PATCH "https://hostwares.com/api/sites/$SITE_ID" \
-H "Authorization: Bearer $HOSTWARES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action": "rollback", "deploymentId": "dep_prev123"}'
exit 1
fi| Scenario | RTO | Action |
|---|---|---|
| Bad code | < 30s | Rollback to previous image |
| Bad env var | < 1 min | Fix var → redeploy or rollback |
| Bad migration | Minutes | Restore DB backup → rollback code (see Backups) |
Monorepos
Set Root directory per site so each app deploys from its package:
# Repo layout
apps/
web/ -> site my-app-web, rootDirectory: apps/web
api/ -> site my-app-api, rootDirectory: apps/api
packages/
ui/ -> shared, not deployed directly
# In CI, only deploy when relevant paths changed
# .github/workflows/web.yml
on:
push:
branches: [main]
paths: ["apps/web/**", "packages/ui/**"]
jobs:
deploy-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm --workspace apps/web test
- run: |
curl -f -X PATCH "https://hostwares.com/api/sites/${{ secrets.WEB_SITE_ID }}" \
-H "Authorization: Bearer ${{ secrets.HOSTWARES_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"action": "deploy"}'Observability
- Build logs — Site → Deployments → [Deploy] → Logs or
GET /api/sites/:id/deployments/:id/logs. - Runtime logs — Site → Logs (tail) + AI log analysis.
- Webhooks — subscribe to
deploy.succeeded/deploy.failedto notify Slack (see Webhooks).
# Notify Slack on deploy result (add as final CI step)
curl -X POST "$SLACK_WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d '{"text": "Deploy to Hostwares: '"$STATUS"' — https://app.example.com"}'
# Or via Hostwares webhooks (server-side, no CI needed)
# Dashboard -> Webhooks -> Add endpoint: https://hooks.slack.com/...
# Events: deploy.succeeded, deploy.failed