Skip to content

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.

  • 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.

Create this layout:

hello-fastapi/
├── app/
│ └── main.py
├── requirements.txt
├── Dockerfile
└── satusky.toml

app/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.1
uvicorn[standard]==0.35.0

Dockerfile:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
EXPOSE 8000
CMD ["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 = 8000
cpu_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.

Run this from the directory that contains satusky.toml:

1ctl deploy --memory 128Mi

The 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.

APP=hello-fastapi
NAMESPACE=$(1ctl -o json app get "$APP" | jq -r '.namespace')
1ctl app status "$APP"
kubectl -n "$NAMESPACE" rollout status "deployment/$APP" --timeout=2m
kubectl -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.

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/health

Stop port-forwarding with Ctrl+C. This is a diagnostic check, not a replacement for the public Gateway smoke test.

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 50

If 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.

When you are done with the example, delete only this application:

1ctl app delete "$APP" --yes

Then confirm its deployment, Service, and Gateway route have been removed:

kubectl -n "$NAMESPACE" get deployment,service,httproute | grep "$APP" || true