Deploy a backend to the cloud
Take the Dockerised Brew & Bean API from your laptop to a public HTTPS URL. Learn the deployment checklist (config, database, health checks, logs) using Render as a worked example that applies to any host.
What you will do: put your FastAPI backend on the internet with a public HTTPS address, connected to a managed Postgres database, configured through environment variables.
Prerequisites: the project from *Containerise your backend with Docker* (a Dockerfile, requirements.txt and app/main.py), a free GitHub account, and Git installed.
We use Render as the worked example because it deploys straight from a GitHub repository and has a free plan for learning. The names of buttons change over time, but the ideas are the same on every host: Railway, Fly.io, Azure App Service, Google Cloud Run, AWS (App Runner, Elastic Beanstalk, ECS) and a plain Linux server. Learn the ideas, not the buttons.
Check the free plan before you rely on it
Free tiers change often. At the time of writing (2026), Render's documentation says free web services spin down after 15 minutes without traffic (the next request waits while it starts again), have a temporary filesystem, and free Postgres databases expire after 30 days. Read the host's current pricing page first. Use free plans for learning and demos, not for something real people depend on. Never enter card details on a service you do not plan to monitor, and set billing alerts on any paid cloud account.
What deployment actually means
Deploying means running your code on a computer that is always on, reachable from the internet, with the right settings. Compare with your laptop:
The server has no .env file you made by hand, so configuration must be supplied as environment variables.
It is not localhost. Users reach it by a domain name over HTTPS, usually through a load balancer or proxy that handles the certificate.
Its disk may be wiped on every deploy, so data belongs in a managed database or object storage, never in local files (this includes SQLite files).
Nobody is watching the terminal, so you need logs and health checks (see *Logging, metrics and health checks*).
Ways to host a backend
| Option | You manage | Good for |
|---|---|---|
| Platform as a service (Render, Railway, Heroku-style, Azure App Service) | Only your code and settings | First deployments, small apps, demos |
| Serverless containers (Google Cloud Run, AWS App Runner, Azure Container Apps) | A container image and its settings | Apps with uneven traffic, scaling to zero |
| Virtual machine (AWS EC2, Azure VM, DigitalOcean droplet) | The operating system, updates, proxy, restarts | Full control, learning Linux |
| Kubernetes (EKS, GKE, AKS) | A lot: clusters, networking, deployments | Large teams with many services |
Start with a platform that deploys from Git. It teaches the essentials without drowning you in infrastructure.
Step 1: make the app deployment-ready
Three small changes make the app friendly to hosting platforms.
1. Listen on the port the platform gives you. Most platforms set a PORT environment variable and expect your app to listen on it. Change the Dockerfile CMD to the shell form so the variable is expanded, with 8000 as the local default.
2. Accept the database URL the platform provides. Many hosts give a URL starting with postgres:// or postgresql://, but SQLAlchemy with the psycopg driver needs postgresql+psycopg://. Normalise it in code.
3. Never depend on local files. The SQLite default in our code is fine on your laptop, but on the server we always set DATABASE_URL.
CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000}"]Replaces the earlier CMD line. ${PORT:-8000} means: use PORT if set, otherwise 8000. The rest of the Dockerfile stays the same.
import os
from sqlalchemy import create_engine
def get_database_url() -> str:
url = os.getenv("DATABASE_URL", "sqlite:///./brewbean.db")
# Hosts often give postgres:// or postgresql://; SQLAlchemy + psycopg needs this prefix
for prefix in ("postgres://", "postgresql://"):
if url.startswith(prefix):
return "postgresql+psycopg://" + url[len(prefix):]
return url
engine = create_engine(get_database_url(), pool_pre_ping=True)Use this in place of the earlier create_engine lines. Everything else in main.py (tables, routes, /healthz, /readyz) stays as it was.
Step 2: push the code to GitHub
Put the project in a repository
- 1Create a
.gitignorecontaining at least.env,.venv,__pycache__and*.db. - 2Run
git init, thengit add ., thengit commit -m "Brew & Bean API". - 3On github.com create a new empty repository (private is fine), then copy the two commands it shows:
git remote add origin <your-repo-url>andgit push -u origin main. If your branch is calledmaster, rungit branch -M mainfirst. - 4Open the repository in your browser and check that
Dockerfile,requirements.txtandapp/are there and.envis not.
Step 3: create the database and the service
The exact screens differ by host, but the order is always: database first, then the service that uses it, so you have a URL to give the service.
Deploy on Render (or the same idea elsewhere)
- 1Sign in to the host with your GitHub account.
- 2Create a PostgreSQL database named
brewbean-db. When it is ready, copy its internal connection URL (internal URLs work only between services on the same host and are faster and safer; use the external URL only when connecting from your laptop). - 3Create a new Web Service and connect your GitHub repository.
- 4Choose the Docker runtime so it builds from your
Dockerfile. Pick the free instance type for practice. - 5Add an environment variable
DATABASE_URLand paste the database URL. - 6Set the health check path to
/healthz. The platform calls it to know when your new version is ready and to restart it if it hangs. - 7Click create or deploy and watch the build logs. When the status is live you get a URL such as
https://brewbean-api.onrender.com.
==> Building image from Dockerfile
Successfully installed fastapi uvicorn sqlalchemy psycopg ...
==> Starting service
INFO: Uvicorn running on http://0.0.0.0:10000
==> Your service is liveThe port number in your log may differ. That is fine: it is the value the platform put in PORT.
Step 4: test the live API
Check it from your laptop
- 1Run
curl https://<your-service-url>/healthz. Expect{"status":"ok"}. On a free plan the first request after idle may take a long time while the service wakes up. - 2Run
curl https://<your-service-url>/readyz. Expect the database to be reported as up. If you get an error, theDATABASE_URLis wrong or the database is not ready. - 3Create an order:
curl -X POST https://<your-service-url>/orders -H "Content-Type: application/json" -d '{"customer_name":"Rahul","item_id":"espresso"}'. - 4Open
https://<your-service-url>/docsin a browser and try the endpoints there. - 5Open the service's Logs page and find the request lines. If you completed the observability lesson, you will see JSON logs with request IDs.
When a deploy fails
Read the build or runtime log from the bottom up. Most first-time failures are: a missing package in requirements.txt, the app listening on a fixed port instead of PORT, a wrong DATABASE_URL, or a wrong module path such as app.main:app. You can reproduce the first three locally with docker run -e PORT=9000 -e DATABASE_URL=... -p 9000:9000 brewbean-api before pushing again.
Step 5: updates, rollbacks and database changes
With Git-based hosting, deploying a change is just git push. The platform rebuilds the image, starts the new version, waits for the health check to pass and then switches traffic. If the health check never passes, the old version keeps running. That is why /healthz matters.
Most platforms keep earlier deploys so you can roll back with one click. Know where that button is before you need it.
Database changes need care. Our demo creates tables at startup, which is fine for learning. In a real project use migrations (Alembic for SQLAlchemy, see *ORMs and migrations*) and run them as a separate step before the new version starts. Make changes in small, backwards-compatible steps: add a column first, deploy code that uses it, and only later remove the old one.
Environments
| Environment | Purpose | Typical settings |
|---|---|---|
| Local | You write and test code | SQLite or Docker Postgres, debug logs |
| Staging | A copy of production for final checks | Separate database, test keys |
| Production | Real users | Strong secrets, backups, monitoring, alerts |
Backups and costs
Free databases usually have no backups and may be deleted after a period. Before real users arrive, move to a paid database with backups and test a restore. If you use a big cloud (AWS, Azure, Google Cloud), set a budget alert first, and shut down anything you created for practice. Forgotten servers and databases are a common source of surprise bills.
Pre-launch checklist
- App listens on the PORT environment variable and on 0.0.0.0.
- All secrets and the database URL come from environment variables, none from Git.
- Health check path is configured and cheap.
- HTTPS works and CORS lists only your real frontend origin (see *API security essentials*).
- Debug mode is off and error responses are generic.
- Logs are readable; you know where to find them.
- Database has backups and migrations are tested.
- You know how to roll back.
How this comes up in interviews. You may be asked: how would you deploy this API; what is the difference between staging and production; why not store uploads or data on the server's disk; how do you manage secrets; what is a rolling deployment; how do you roll back a bad release. Answer from your own experience of having deployed the Brew & Bean API.
My [FastAPI / Express] app deployed on [Render / Railway / other host] fails to start. Here are the last 40 lines of the deploy log:
[paste log]
My start command is: [command]
Environment variables I set (names only, not values): [list]
Explain the most likely cause in plain English, then give the exact change to fix it. If you need more information, ask me one question first.What to expect: Never paste real passwords or API keys into an AI chat; replace them with placeholders. A good answer points to a specific line of the log.
Try it: Deploy a change and practise a rollback
1. Add a GET /menu route that returns the eight Brew & Bean items from the shared menu (id, name, category, price).
2. Commit and push, then watch the platform deploy it. Test /menu on the live URL.
3. Deliberately break the app (for example change the module name in the start command), push, and observe that the health check fails and the old version keeps serving.
4. Fix it, or use the host's rollback feature, and note how long recovery took.
Key takeaways
- Deploying means running your container on an always-on host with environment-variable configuration, HTTPS and a managed database.
- Listen on the PORT variable and 0.0.0.0; keep data out of the server's local disk.
- Create the database first, then the service, then set DATABASE_URL.
- A health check path lets the platform confirm a release is good and restart a broken one.
- Deploy by pushing to Git, know how to roll back, and use migrations for database changes.
- Check free-tier limits (sleep, expiry, no backups) before depending on them, and set billing alerts on paid clouds.
Check yourself
Finishing the quiz marks the lesson as done.
1. Why should a deployed API not store customer data in a local SQLite file on the server?
2. What is the purpose of the health check path you set on the platform?
3. Your host gives DATABASE_URL starting with postgres://. SQLAlchemy with psycopg fails to connect. What is a good fix?
4. When should you use the internal database URL instead of the external one?
5. Which approach to a database column removal is safest in production?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.