Deploy a Python FastAPI App
This guide deploys a small FastAPI API through SatuSky’s cloud builder. It is deliberately self-contained: no database, queue, external API, or secret is required to prove the Python, health-check, Service, and Gateway path.
Prerequisites
Section titled “Prerequisites”- 1ctl is installed and authenticated. Confirm the active organization with 1ctl auth status.
- kubectl access to the active organization’s namespace is optional but recommended for the Kubernetes verification step.
- A unique application name. Names are shared within an organization namespace.
The commands below use hello-fastapi; replace it with a unique, lowercase name before deploying.
1. Create the FastAPI project
Section titled “1. Create the FastAPI project”Create this layout:
hello-fastapi/├── app/│ └── main.py├── requirements.txt├── Dockerfile└── satusky.tomlapp/main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")def health() -> dict[str, str]: return {"status": "ok", "service": "python-fastapi"}
@app.get("/")def root() -> dict[str, str]: return {"message": "Hello from FastAPI"}The /health endpoint is intentionally small and dependency-free. It is the endpoint SatuSky probes after deployment, and it gives you an unambiguous public smoke test. FastAPI also exposes interactive API documentation at /docs.
requirements.txt:
fastapi==0.116.1uvicorn[standard]==0.35.0Dockerfile:
FROM python:3.12-slim
WORKDIR /appCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txtCOPY app ./app
EXPOSE 8000CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]Uvicorn must bind to 0.0.0.0. Binding to the default loopback address makes the process reachable only inside the container, so the Kubernetes Service and health probe cannot reach it.
satusky.toml:
[app]name = "hello-fastapi"port = 8000cpu_request = "100m"cpu_limit = "250m"
[checks]health_path = "/health"Keep credentials out of this file. Add a database URL or API key later with 1ctl secret create; this example has no secrets.
2. Build and deploy
Section titled “2. Build and deploy”Run this from the directory that contains satusky.toml:
1ctl deploy --memory 128MiThe CLI packages the local source and runs the Docker build in SatuSky’s cloud builder; Docker does not need to be installed on your computer. The command prints the deployment ID and a generated https://…satusky.com domain. Save neither by hand: the commands below resolve the current application record.
The explicit –memory 128Mi flag sets the container memory limit for this small example. Use a larger value for an application with heavier dependencies.
3. Verify the deployment record and pod
Section titled “3. Verify the deployment record and pod”APP=hello-fastapiNAMESPACE=$(1ctl -o json app get "$APP" | jq -r '.namespace')
1ctl app status "$APP"kubectl -n "$NAMESPACE" rollout status "deployment/$APP" --timeout=2mkubectl -n "$NAMESPACE" get pod -l "app=$APP"kubectl -n "$NAMESPACE" get service "$APP"The deployment is ready when the rollout reports success, the pod is Running and ready, and the Service has an endpoint:
kubectl -n "$NAMESPACE" get endpointslice \ -l "kubernetes.io/service-name=$APP"Verify public routing through the Kubernetes Gateway API. A healthy Gateway-era deployment has an HTTPRoute for its generated domain; you do not need to create an Ingress.
kubectl -n "$NAMESPACE" get httproute "$APP-route"If the deployment is running but the HTTPRoute is absent, or its DNS condition
is not verified, wait and check again. Do not create a competing Ingress
manually: the platform-owned route is the public contract. If the route does
not appear after the deployment controller has reconciled, keep the deployment
for diagnosis and contact platform support with the deployment ID.
4. Call the public FastAPI endpoint
Section titled “4. Call the public FastAPI endpoint”Once the route is attached and the DNS condition is verified, resolve the
generated URL from the application record and call the health endpoint:
APP_URL=$(1ctl -o json app get "$APP" | jq -r '.domain')curl -fsS "$APP_URL/health"The response is:
{"status":"ok","service":"python-fastapi"}You can also open $APP_URL/docs in a browser to inspect FastAPI’s generated OpenAPI UI.
If the public request fails, distinguish the layers before changing application code:
| Check | What it proves |
|---|---|
| kubectl rollout status deployment/$APP | The FastAPI container is ready. |
| kubectl get endpointslice -l kubernetes.io/service-name=$APP | The Service has a ready pod target. |
| kubectl get httproute $APP-route | Gateway routing was reconciled. |
| curl -fsS “$APP_URL/health” | DNS, Gateway, TLS, Service, and FastAPI all work together. |
For a container-level check while the public route is still propagating, a cluster operator can temporarily run:
kubectl -n "$NAMESPACE" port-forward "service/$APP" 18000:8000# In a second terminal:curl -fsS http://127.0.0.1:18000/healthStop port-forwarding with Ctrl+C. This is a diagnostic check, not a replacement for the public Gateway smoke test.
5. Inspect the deployed record and logs
Section titled “5. Inspect the deployed record and logs”The global -o json flag must appear before the command:
1ctl -o json app get "$APP" | jq '{deployment_id, status, image, port, domain}'1ctl logs --app "$APP" --tail 50If Uvicorn fails to start, the most common causes are an incorrect module path in CMD, a missing package in requirements.txt, or binding to 127.0.0.1. The log command is the first place to inspect those failures.
6. Clean up
Section titled “6. Clean up”When you are done with the example, delete only this application:
1ctl app delete "$APP" --yesThen confirm its deployment, Service, and Gateway route have been removed:
kubectl -n "$NAMESPACE" get deployment,service,httproute | grep "$APP" || trueNext steps
Section titled “Next steps”- Environment Configuration — add non-sensitive runtime settings.
- API with a Database — connect an API service to a database.
- Custom Domains — attach a domain you own.