{
  "manifest_version": "1",
  "slug": "fastapi",
  "title": "FastAPI",
  "headline": "Deploy a FastAPI app",
  "description": "Deploy a FastAPI app to an exe.dev VM: git push to deploy, uv and systemd, SQLite on a persistent disk, private /docs, and HTTPS from exe.dev.",
  "category": "frameworks",
  "tags": [
    "fastapi",
    "python",
    "uv",
    "uvicorn",
    "sqlmodel",
    "sqlite",
    "alembic",
    "git push deploy",
    "heroku alternative"
  ],
  "url": "https://exe.dev/deploy/fastapi",
  "markdown_url": "https://exe.dev/deploy/fastapi.md",
  "upstream": {
    "website": "https://fastapi.tiangolo.com",
    "source": "https://github.com/fastapi/fastapi",
    "docs": "https://fastapi.tiangolo.com/deployment/",
    "license": "MIT"
  },
  "example_vm_name": "my-fastapi",
  "time": "10 minutes",
  "stack": [
    {
      "name": "Your FastAPI app",
      "detail": "`fastapi run` (Uvicorn) with one worker per CPU, as `app.service` under systemd",
      "port": 8000,
      "data": "~/app"
    },
    {
      "name": "Python 3.14 and uv",
      "detail": "uv installs Python and your locked dependencies into `~/app/.venv`"
    },
    {
      "name": "SQLite",
      "detail": "SQLModel and Alembic migrations in the starter app",
      "data": "~/data"
    },
    {
      "name": "Deploy hook",
      "detail": "`git push` installs, migrates, restarts, and checks; a failure rejects the push",
      "data": "~/app.git"
    },
    {
      "name": "Nightly backup",
      "detail": "a systemd timer; keeps two weeks",
      "data": "~/backups"
    }
  ],
  "access": [
    {
      "url": "https://my-fastapi.exe.xyz/",
      "who": "private",
      "what": "your API, for you, people you share it with, and scripts with a token"
    },
    {
      "url": "https://my-fastapi.exe.xyz/docs",
      "who": "private",
      "what": "the interactive API docs"
    },
    {
      "url": "my-fastapi.exe.xyz:app.git",
      "who": "private",
      "what": "`git push exe main` deploys"
    }
  ],
  "inputs": [
    {
      "name": "REPO",
      "description": "the GitHub repository to deploy, as owner/name or a URL, or `new` to start a fresh FastAPI app"
    }
  ],
  "delivers": [
    "Your app, or a new FastAPI + SQLModel starter, running as `app.service` on port 8000 and enabled to start after a reboot.",
    "`git push exe main` deploys: the hook builds a new release in `~/releases` (uv, Alembic migrations), switches `~/app` to it, restarts, and checks the app answers. A failed deploy rejects the push and leaves the running version up.",
    "exe.dev's proxy headers trusted from 127.0.0.1 (the Uvicorn flags in `ExecStart`), so redirects and generated URLs use `https://\u003cvm\u003e.exe.xyz`. For a server without such flags, the owner is told what to change.",
    "Data in `~/data` (SQLite for the starter), backed up every night to `~/backups`, with a backup restored and checked in a scratch directory.",
    "The interactive API docs at `https://\u003cvm\u003e.exe.xyz/docs`, private behind exe.dev login."
  ],
  "owner_runs_after": [
    "git clone -o exe my-fastapi.exe.xyz:app.git    # if you started fresh",
    "git remote add exe my-fastapi.exe.xyz:app.git  # or, in your own app's checkout"
  ],
  "deployable": true,
  "deploy": {
    "new_vm_url": "https://exe.dev/new?prompt=REPO%3D%3Cfill-this-in%3E%0A%0ASet%20up%20FastAPI%20on%20this%20VM%20by%20following%20the%20exe.dev%20recipe%20at%20https%3A%2F%2Fexe.dev%2Fdeploy%2Ffastapi.md%20from%20top%20to%20bottom.%0A%0AThe%20values%20at%20the%20top%20of%20this%20message%20are%20mine%3A%0A-%20REPO%3A%20the%20GitHub%20repository%20to%20deploy%2C%20as%20owner%2Fname%20or%20a%20URL%2C%20or%20%60new%60%20to%20start%20a%20fresh%20FastAPI%20app%0AAsk%20me%20for%20any%20that%20still%20say%20%3Cfill-this-in%3E%20before%20you%20need%20them.%0A%0AThe%20recipe%27s%20example%20VM%20name%20is%20my-fastapi.%20This%20VM%27s%20name%20is%20the%20output%20of%20%60hostname%60%3A%20use%20it%20wherever%20the%20recipe%20writes%20my-fastapi.%20Skip%20the%20step%20that%20creates%20the%20VM%3B%20you%20are%20on%20it.%0A%0AYou%20are%20done%20when%20all%20of%20these%20are%20true.%20Check%20each%20one%20yourself%3A%0A-%20Your%20app%2C%20or%20a%20new%20FastAPI%20%2B%20SQLModel%20starter%2C%20running%20as%20%60app.service%60%20on%20port%208000%20and%20enabled%20to%20start%20after%20a%20reboot.%0A-%20%60git%20push%20exe%20main%60%20deploys%3A%20the%20hook%20builds%20a%20new%20release%20in%20%60~%2Freleases%60%20%28uv%2C%20Alembic%20migrations%29%2C%20switches%20%60~%2Fapp%60%20to%20it%2C%20restarts%2C%20and%20checks%20the%20app%20answers.%20A%20failed%20deploy%20rejects%20the%20push%20and%20leaves%20the%20running%20version%20up.%0A-%20exe.dev%27s%20proxy%20headers%20trusted%20from%20127.0.0.1%20%28the%20Uvicorn%20flags%20in%20%60ExecStart%60%29%2C%20so%20redirects%20and%20generated%20URLs%20use%20%60https%3A%2F%2F%3Cvm%3E.exe.xyz%60.%20For%20a%20server%20without%20such%20flags%2C%20the%20owner%20is%20told%20what%20to%20change.%0A-%20Data%20in%20%60~%2Fdata%60%20%28SQLite%20for%20the%20starter%29%2C%20backed%20up%20every%20night%20to%20%60~%2Fbackups%60%2C%20with%20a%20backup%20restored%20and%20checked%20in%20a%20scratch%20directory.%0A-%20The%20interactive%20API%20docs%20at%20%60https%3A%2F%2F%3Cvm%3E.exe.xyz%2Fdocs%60%2C%20private%20behind%20exe.dev%20login.%0A%0ARead%20the%20whole%20recipe%2C%20including%20the%20closed%20details%2C%20and%20follow%20%22Set%20up%0Aby%20hand%22%20in%20order%2C%20skipping%20VM%20creation.%20The%20walkthrough%20above%20it%20is%20for%0Athe%20owner%20afterwards.%20Ask%20for%20REPO%20first%20if%20it%20says%20%3Cfill-this-in%3E.%0AFor%20REPO%3Dnew%2C%20build%20the%20starter%20exactly%20as%20written.%20For%20a%20repository%2C%0Afollow%20%22Bring%20your%20app%22%3A%20adapt%20ExecStart%2C%20~%2Fapp.env%2C%20and%20the%20hook%27s%0Ainstall%20step%20to%20the%20app.%20Never%20commit%20to%20the%20owner%27s%20repository%20or%20push%0Ato%20~%2Fapp.git%20%28run%20the%20hook%20by%20hand%20instead%29%2C%20so%20the%20owner%27s%20first%20push%0Afast-forwards%2C%20and%20keep%20the%20hook%20deploying%20the%20repository%27s%20default%0Abranch.%20Remove%20whatever%20you%20make%20only%20for%20testing.%20If%20a%20private%0Arepository%20can%27t%20be%20cloned%2C%20give%20the%20owner%20the%20%60integrations%20add%20github%60%0Acommand%20with%20this%20VM%27s%20name%20and%20wait%20for%20them.%0ADon%27t%20reboot%2C%20don%27t%20make%20the%20VM%20public%2C%20and%20don%27t%20run%20the%20Restore%20steps%0Aon%20live%20data%3A%20test%20the%20backup%20in%20a%20scratch%20directory%20as%20written.%0AThe%20VM%20can%27t%20open%20its%20own%20https%3A%2F%2F%3Cvm%3E.exe.xyz%3B%20%22Check%20it%22%20tests%20the%0Aproxy%27s%20view%20through%20localhost%20instead.%0AFinish%20with%20https%3A%2F%2F%3Cvm%3E.exe.xyz%2Fdocs%20and%20only%20the%20one%20git%20command%20from%0A%22Deploy%20with%20git%20push%22%20that%20fits%3A%20%60git%20clone%20-o%20exe%60%20for%20REPO%3Dnew%2C%0A%60git%20remote%20add%20exe%60%20for%20a%20repository%2C%20plus%20the%20branch%20to%20push%20if%20it%0Aisn%27t%20main%2C%20and%20anything%20you%20adapted%20or%20that%20the%20owner%20should%20change.%0A%0AFinish%20with%20the%20URLs%20I%20should%20open%20and%20these%20commands%2C%20which%20only%20I%20can%20run%2C%20from%20my%20own%20computer%20%28with%20this%20VM%27s%20name%20in%20them%29%3A%0A%20%20git%20clone%20-o%20exe%20my-fastapi.exe.xyz%3Aapp.git%20%20%20%20%23%20if%20you%20started%20fresh%0A%20%20git%20remote%20add%20exe%20my-fastapi.exe.xyz%3Aapp.git%20%20%23%20or%2C%20in%20your%20own%20app%27s%20checkout%0A",
    "terminal": "curl -fsSL https://exe.dev/deploy/fastapi/prompt.txt | ssh exe.dev new --prompt=/dev/stdin",
    "prompt": "REPO=\u003cfill-this-in\u003e\n\nSet up FastAPI on this VM by following the exe.dev recipe at https://exe.dev/deploy/fastapi.md from top to bottom.\n\nThe values at the top of this message are mine:\n- REPO: the GitHub repository to deploy, as owner/name or a URL, or `new` to start a fresh FastAPI app\nAsk me for any that still say \u003cfill-this-in\u003e before you need them.\n\nThe recipe's example VM name is my-fastapi. This VM's name is the output of `hostname`: use it wherever the recipe writes my-fastapi. Skip the step that creates the VM; you are on it.\n\nYou are done when all of these are true. Check each one yourself:\n- Your app, or a new FastAPI + SQLModel starter, running as `app.service` on port 8000 and enabled to start after a reboot.\n- `git push exe main` deploys: the hook builds a new release in `~/releases` (uv, Alembic migrations), switches `~/app` to it, restarts, and checks the app answers. A failed deploy rejects the push and leaves the running version up.\n- exe.dev's proxy headers trusted from 127.0.0.1 (the Uvicorn flags in `ExecStart`), so redirects and generated URLs use `https://\u003cvm\u003e.exe.xyz`. For a server without such flags, the owner is told what to change.\n- Data in `~/data` (SQLite for the starter), backed up every night to `~/backups`, with a backup restored and checked in a scratch directory.\n- The interactive API docs at `https://\u003cvm\u003e.exe.xyz/docs`, private behind exe.dev login.\n\nRead the whole recipe, including the closed details, and follow \"Set up\nby hand\" in order, skipping VM creation. The walkthrough above it is for\nthe owner afterwards. Ask for REPO first if it says \u003cfill-this-in\u003e.\nFor REPO=new, build the starter exactly as written. For a repository,\nfollow \"Bring your app\": adapt ExecStart, ~/app.env, and the hook's\ninstall step to the app. Never commit to the owner's repository or push\nto ~/app.git (run the hook by hand instead), so the owner's first push\nfast-forwards, and keep the hook deploying the repository's default\nbranch. Remove whatever you make only for testing. If a private\nrepository can't be cloned, give the owner the `integrations add github`\ncommand with this VM's name and wait for them.\nDon't reboot, don't make the VM public, and don't run the Restore steps\non live data: test the backup in a scratch directory as written.\nThe VM can't open its own https://\u003cvm\u003e.exe.xyz; \"Check it\" tests the\nproxy's view through localhost instead.\nFinish with https://\u003cvm\u003e.exe.xyz/docs and only the one git command from\n\"Deploy with git push\" that fits: `git clone -o exe` for REPO=new,\n`git remote add exe` for a repository, plus the branch to push if it\nisn't main, and anything you adapted or that the owner should change.\n\nFinish with the URLs I should open and these commands, which only I can run, from my own computer (with this VM's name in them):\n  git clone -o exe my-fastapi.exe.xyz:app.git    # if you started fresh\n  git remote add exe my-fastapi.exe.xyz:app.git  # or, in your own app's checkout\n",
    "prompt_url": "https://exe.dev/deploy/fastapi/prompt.txt"
  },
  "updated": "2026-10-17",
  "related": [
    "django",
    "postgres",
    "nextjs",
    "claude-code",
    "docker-compose",
    "laravel"
  ],
  "alternatives": [
    "Railway",
    "Render",
    "Heroku",
    "Fly.io"
  ]
}
