motihost

Docs

Every site is an S3 bucket. Upload your build output, and motihost deploys it.

1. Create a site and a key

In the dashboard, create a site: its name is also its bucket name. Then create an access key on the site's Deploy tab. The secret is shown once. Keys are permanent until you revoke them, and each key works for one site only.

2. AWS CLI

aws configure set aws_access_key_id MH... --profile motihost
aws configure set aws_secret_access_key ... --profile motihost
aws configure set region eu-central-1 --profile motihost
aws configure set s3.addressing_style path --profile motihost

# Deploy (sync one folder that holds everything)
aws s3 sync ./out s3://my-site --delete --profile motihost --endpoint-url https://s3.hst.motivast.com

3. rclone

# ~/.config/rclone/rclone.conf
[motihost]
type = s3
provider = Other
access_key_id = MH...
secret_access_key = ...
endpoint = https://s3.hst.motivast.com
region = eu-central-1
force_path_style = true

rclone sync ./out motihost:my-site --checksum

4. Cyberduck

Open a connection of type Amazon S3, set the server to s3.hst.motivast.com, and paste your access key ID and secret. Your site appears as a single bucket.

5. GitHub Actions

# .github/workflows/deploy.yml
on: { push: { branches: [main] } }
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build
      - run: aws s3 sync ./out s3://my-site --delete --endpoint-url https://s3.hst.motivast.com
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.MOTIHOST_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.MOTIHOST_SECRET }}
          AWS_DEFAULT_REGION: eu-central-1
          AWS_S3_ADDRESSING_STYLE: path

6. Folder layout

out/
  index.html, assets/…   # static files (or put them under publicDir)
  functions/*.mjs        # pre-bundled handlers, served at /api/<name>
  motihost.json          # optional settings

functions/, motihost.json and .motihost/ are never served as files. Sync one folder that holds everything: aws s3 sync --delete of only dist/ would delete your functions.

7. motihost.json

{
  "publicDir": "",
  "spa": true,
  "redirects": [{ "from": "/old/*", "to": "/new/:splat", "status": 301 }],
  "headers": [{ "source": "/*", "headers": { "X-Frame-Options": "DENY" } }],
  "functions": { "memoryMb": 256, "timeoutSeconds": 10 }
}

Paths resolve in this order: redirects, the exact file, /index.html, .html (clean URLs), the SPA fallback, then your 404.html. Files with a hash in the name are cached for a year; HTML revalidates every time.

8. Functions

// functions/hello.mjs → /api/hello   (bundle dependencies first, e.g. with esbuild)
export default {
  async fetch(request, env) {
    return Response.json({ hello: "world", key: env.MY_SECRET ? "set" : "missing" });
  },
};

functions/users/index.mjs answers /api/users, and functions/users/[...].mjs answers everything below it. Set environment variables on the site's Functions tab, then deploy again. console.log output appears in the dashboard (4 KB per request).

9. Deploys

10. Limits

Allowances are shared by all sites in your account; you can cap a single site in its Settings. Past 100% sites slow down instead of costing you more. See pricing.