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
- Automatic: a deploy starts about 15 seconds after your last upload, once no multipart upload is still open.
- Manual: upload
.motihost/deploy(echo go | aws s3 cp - s3://my-site/.motihost/deploy), or click Deploy in the dashboard. Turn automatic deploys off in Settings. - Visitors see the old version or the new one, never a mix. Roll back to any kept deployment with one click.
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.