Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.github
.venv
venv
__pycache__
*.pyc
.pytest_cache
test_main.py
requirements-dev.txt
README.md
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.14"
- run: pip install -r requirements-dev.txt
- run: pytest
- run: docker build -t fastapi-getting-started .
28 changes: 0 additions & 28 deletions .github/workflows/porter_stack_fastapi-getting-started.yml

This file was deleted.

20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.14-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY main.py .

RUN useradd --create-home --uid 10001 app
USER app

EXPOSE 8000

CMD ["python", "main.py"]
12 changes: 0 additions & 12 deletions Pipfile

This file was deleted.

803 changes: 0 additions & 803 deletions Pipfile.lock

This file was deleted.

69 changes: 67 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,67 @@
# fastapi-getting-started
A sample app for getting started with FastAPI on Porter Cloud.
# FastAPI on Porter

A minimal FastAPI app, served by Uvicorn, ready to deploy on Porter.

## What's inside

- Python 3.14, FastAPI 0.157.0, Uvicorn 0.54.0
- A Dockerfile based on `python:3.14-slim` that runs as a non-root user

| Path | Method | Description |
| --- | --- | --- |
| `/` | GET | Confirms the app is running |
| `/healthz` | GET | Health check used by Porter |
| `/hello/{name}` | GET | Example route with a path parameter |
| `/docs` | GET | Interactive API docs generated by FastAPI |

## Run locally

```sh
python3.14 -m venv .venv
. .venv/bin/activate
pip install -r requirements-dev.txt
uvicorn main:app --reload --port 8000
pytest
```

With Docker:

```sh
docker build -t fastapi-getting-started . && docker run -p 8000:8000 fastapi-getting-started
```

Open http://localhost:8000 and http://localhost:8000/docs.

## Deploy on Porter

### From the dashboard

Fork this repo, then in https://dashboard.porter.run create a new app from your GitHub repo. Porter reads `porter.yaml`, builds the Dockerfile, and opens a PR adding a GitHub Actions workflow. Merge it and every push to `main` redeploys. See https://docs.porter.run/applications/deploy/deploy-from-github-repo.

### From the CLI

Install the CLI (https://docs.porter.run/cli/installation), then:

```sh
porter auth login
porter apply -f porter.yaml
```

## Configuration

`porter.yaml` defines one `web` service. It builds the Dockerfile at the repo root, runs Uvicorn on port 8000 with 0.25 CPU cores and 256 MB of memory, and uses `/healthz` as its health check. Full reference: https://docs.porter.run/applications/configuration-as-code/reference.

| Name | Required | Description |
| --- | --- | --- |
| `PORT` | No | Port Uvicorn listens on. Defaults to `8000`, which matches `port` in `porter.yaml`. |

Dependencies are declared in `requirements.in` and pinned in `requirements.txt`. To upgrade, run `uv pip compile requirements.in -o requirements.txt --upgrade`.

## Learn more

- https://docs.porter.run/getting-started/quickstart
- https://docs.porter.run/applications/configuration-as-code/overview
- https://docs.porter.run/applications/configure/environment-groups
- https://docs.porter.run/addons/datastores
- https://docs.porter.run/preview-environments/overview
- https://github.com/porter-dev/starters
23 changes: 18 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import os

import uvicorn
from fastapi import FastAPI

app = FastAPI()
app = FastAPI(title="FastAPI on Porter")


@app.get("/")
async def root():
return {"author": "rudimk", "message": "in remembrance of earth's past."}
return {"message": "FastAPI is running on Porter"}

@app.get("/info")
async def root():
return {"author": "rudimk", "message": "สวัสดี"}

@app.get("/healthz")
async def healthz():
return {"status": "ok"}


@app.get("/hello/{name}")
async def hello(name: str):
return {"message": f"Hello, {name}!"}


if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8000")))
16 changes: 16 additions & 0 deletions porter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: v2
name: fastapi-getting-started
build:
method: docker
context: .
dockerfile: ./Dockerfile
services:
- name: web
type: web
run: python main.py
port: 8000
cpuCores: 0.25
ramMegabytes: 256
healthCheck:
enabled: true
httpPath: /healthz
3 changes: 3 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-r requirements.txt
pytest==9.1.1
httpx2==2.16.0
2 changes: 2 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fastapi==0.157.0
uvicorn[standard]==0.54.0
86 changes: 50 additions & 36 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
annotated-types==0.6.0
anyio==4.3.0
certifi==2024.2.2
click==8.1.7
dnspython==2.6.1
email_validator==2.1.1
exceptiongroup==1.2.1
fastapi==0.111.0
fastapi-cli==0.0.2
h11==0.14.0
httpcore==1.0.5
httptools==0.6.1
httpx==0.27.0
idna==3.7
Jinja2==3.1.3
markdown-it-py==3.0.0
MarkupSafe==2.1.5
mdurl==0.1.2
orjson==3.10.2
pydantic==2.7.1
pydantic_core==2.18.2
Pygments==2.17.2
python-dotenv==1.0.1
python-multipart==0.0.9
PyYAML==6.0.1
rich==13.7.1
shellingham==1.5.4
sniffio==1.3.1
starlette==0.37.2
typer==0.12.3
typing_extensions==4.11.0
ujson==5.9.0
uvicorn==0.29.0
uvloop==0.19.0
watchfiles==0.21.0
websockets==12.0
annotated-doc==0.0.5
# via fastapi
annotated-types==0.8.0
# via pydantic
anyio==4.15.1
# via
# starlette
# watchfiles
click==8.5.0
# via uvicorn
fastapi==0.157.0
# via -r requirements.in
h11==0.16.0
# via uvicorn
httptools==0.9.0
# via uvicorn
idna==3.20
# via anyio
opentelemetry-api==1.45.1
# via fastapi
pydantic==2.14.1
# via fastapi
pydantic-core==2.50.1
# via pydantic
python-dotenv==1.2.4
# via uvicorn
pyyaml==6.0.3
# via uvicorn
starlette==1.9.0
# via fastapi
typing-extensions==4.16.0
# via
# anyio
# fastapi
# opentelemetry-api
# pydantic
# pydantic-core
# typing-inspection
typing-inspection==0.4.4
# via
# fastapi
# pydantic
uvicorn==0.54.0
# via -r requirements.in
uvloop==0.23.0
# via uvicorn
watchfiles==1.3.0
# via uvicorn
websockets==17.3
# via uvicorn
11 changes: 11 additions & 0 deletions test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from fastapi.testclient import TestClient

from main import app

client = TestClient(app)


def test_endpoints():
assert client.get("/").json() == {"message": "FastAPI is running on Porter"}
assert client.get("/healthz").json() == {"status": "ok"}
assert client.get("/hello/porter").json() == {"message": "Hello, porter!"}
Loading