Skip to content

Publish safely

  • Lesson 20
  • 45 min
  • Module 7: Deploy and verify

What you will be able to do

Put the API on the internet, with an HTTPS address that any program can call, and with its key protecting it from the first second. Nothing secret is in the code, in a file, or in your terminal history.

Before you startWhat to know and to have readyAbout 45 minutes

What you need to know

What you need on your computer

The steps follow FastAPI Cloud’s documentation and the help of fastapi-cloud-cli 0.26.

This deployment is for practice. Treat it as disposable: you make it, check it, and delete it at the end of the next lesson. Three rules follow from that:

  1. It gets its own API key, made for it, never one that you use elsewhere.
  2. It has no real data. The classifier is the keyword mock.
  3. You know exactly what you made, so you can remove all of it.

A platform must know two different things about your app:

Check Question Endpoint If it fails
Liveness Is the process running at all? /health Restart the app.
Readiness Can it do its job now? /ready Do not send it requests yet, but do not restart it.

The difference matters when the classifier is down. The process is fine, so restarting it does not help, and a restart loop makes things worse. But a client should know that classification does not work now. /health stays 200, and /ready gives 503.

/ready checks the real work: it asks the classifier for one prediction. Add it to main.py, next to /health:

ticket_api/main.py (in create_app, after /health)
@app.get(
"/ready",
tags=["health"],
summary="Check that the service can classify tickets",
responses={503: {"model": ErrorResponse, "description": "Not ready."}},
)
def ready() -> Readiness:
app.state.classifier.predict("readiness check")
return Readiness(status="ready")
ticket_api/models.py (add)
class Readiness(BaseModel):
status: Literal["ready"]

Import ErrorResponse and Readiness in main.py. If the classifier raises ClassifierUnavailable, the handler from Module 6 answers 503, in the standard shape. Add a test, then update the reviewed contract:

tests/test_classify.py (add)
def test_ready_and_not_ready(make_client):
assert make_client().get("/ready").status_code == 200
not_ready = make_client(classifier_mode="unavailable").get("/ready")
assert not_ready.status_code == 503
  1. Make sure that your virtual environment is active. The fastapi command that deploys comes with fastapi[standard].

  2. Sign in:

    Terminal window
    fastapi login

    A browser window opens. Create your account, or sign in, and allow the command line to use it. The command waits, then says that you are signed in.

  3. In the project folder, create the app, and link the folder to it:

    Terminal window
    fastapi cloud apps create --name ticket-api --link

    If the name is taken, choose another one, for example ticket-api-yourname. The command writes the app’s ID to .fastapicloud/cloud.json, so that the next commands know which app to use.

You create the app before the first deployment on purpose: then you can give it its key before any code runs. If you deploy first, the API is public for a few minutes without a key. The warning API_KEY is not set in its log tells everyone who reads the log so.

Make a long random key with Python’s secrets module. Send it to the platform without typing it, so it is not in your terminal history:

Terminal window
KEY=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
printf %s "$KEY" | fastapi cloud env set API_KEY --secret --value-stdin --no-redeploy
fastapi cloud env set SHOW_DOCS false --no-redeploy
echo "$KEY"
Part Why
secrets.token_urlsafe(32) 32 random bytes as text, about 43 characters. Nobody can guess it.
KEY=$(...) Keeps the key in a variable of this terminal only.
--value-stdin Reads the value from the `
--secret FastAPI Cloud encrypts the value. Nobody can read it again, not even you, in the dashboard or the CLI.
--no-redeploy Saves the setting without a deployment. There is nothing to deploy yet.
SHOW_DOCS false The help desk system is the only client, so the documentation pages are off in production, as decided in Module 4. Not a secret.
The last line Shows the key once, so you can copy it into Postman in the next lesson. Copy it now, then close this terminal.

Because the secret cannot be read again, if you lose the key, you set a new one. That is also how you replace a key that leaked.

Terminal
fastapi deploy

The command packs the project folder, sends it, and waits while the platform builds and starts the app. It leaves out .venv, caches, .git, every .env file, and the files that .gitignore lists. At the end, it prints the app’s address, which ends in .fastapicloud.dev. Write the address down.

Then check it, with HTTPS, from your terminal (replace the address with yours):

Terminal (on Windows: curl.exe)
curl -i https://ticket-api.fastapicloud.dev/health
curl -i https://ticket-api.fastapicloud.dev/ready
curl -i https://ticket-api.fastapicloud.dev/docs

You expect 200, 200 and 404. HTTPS works without any setup: the platform has a certificate for its addresses. Each response has an X-Request-ID header, from your middleware.

  1. Add /ready, its model and its test. Run the tests, review the contract difference (a new endpoint), and export the contract.
  2. In Postman, duplicate Check that the service runs as Check that the service can classify tickets, with the URL {{baseUrl}}/ready. Give it these tests: status is 200, and status is ready. Export the collection again into postman/.
  3. Check .gitignore: it lists .venv/, __pycache__/, .env and .fastapicloud/.
  4. Sign in with fastapi login, and create the linked app with fastapi cloud apps create --name ticket-api --link.
  5. Make the key, and set API_KEY (secret) and SHOW_DOCS, both with --no-redeploy. Copy the key that the last command shows, and close the terminal.
  6. Open a new terminal, activate the environment, and run fastapi deploy. Write down the address.
  7. Call /health, /ready and /docs with curl. Check the three status codes and the X-Request-ID header.
  8. Send a classify request without a key: 401. The key protects the API from the first deployment.

Open FastAPI Cloud’s dashboard in your browser, and find your app. Without changing anything, find:

  1. The app’s address.
  2. The environment variables. Can you see the value of API_KEY? Of SHOW_DOCS?
  3. The latest deployment and its status.
Show what you should see

The address is the one that fastapi deploy printed. Under Environment Variables, SHOW_DOCS shows false, and API_KEY shows that it is a secret, without its value: secrets cannot be viewed after they are saved. The deployment is listed with its status. The dashboard can do the same as the CLI commands, and the next lesson uses it to delete the app.

Common mistake

Deployed, but every classify request gets 401, even with the key

Symptom: /health works, but classify requests get 401 with the key that you copied.

Cause: the key that the app has is not the key that you send. There are some common reasons. The key was set after the deployment with --no-redeploy, so the running app does not have it yet. Or a space or line break was copied with it. Or a second env set replaced it with a new random key.

Fix:

  1. Check the app’s log for the start message API_KEY is not set (next lesson: fastapi cloud logs). If it is there, deploy again: fastapi deploy.
  2. If you are not sure which key the app has, set a new one, and use the new key. Use step 2, without --no-redeploy this time, so the app restarts with it.
  3. Do not set the key without --secret, to make it visible. A key that can be read is a key that can leak.

Check your understanding

Why are the app and its secret created before the first fastapi deploy, instead of deploying first and adding the key afterwards?

Show the answer

The app reads API_KEY at start. Without it, /v1/classify accepts requests from anyone, and the address is public as soon as the deployment ends. Creating the app and its secret first means that the very first running version is already protected. There is never a window, however short, where the API is open.

Check your understanding

The classifier is down. What do /health and /ready return, and what should the platform do?

Show the answer

/health returns 200: the process runs, so restarting it would not help. /ready returns 503 with classifier_unavailable: the service cannot do its job now. The platform, or a monitor, should stop sending classify traffic or alert someone, but not restart the app in a loop.

Recap

  • A practice deployment is disposable: its own key, no real data, and you delete it afterwards.
  • /health is liveness (restart if it fails). /ready is readiness (it can do its job now).
  • Create the app, then set its secrets with --secret --value-stdin, then deploy. The first running version is already protected.
  • Secrets on the platform cannot be read again. To replace a key, set a new one.
  • The platform gives you HTTPS. Check /health, /ready and a request without a key after each deployment.

Next, you check the deployed API with the Postman collection, read its logs, and remove it.

Next: Verify and clean up