Preparing your learning space...
100% through FDE Projects tutorials
A Production-Ready FDE Platform is a prototype that has been hardened enough that a customer can rely on it daily. As a Forward Deployed Engineer you'll build a working demo quickly, but the job only finishes when that demo survives real users, real data, and real failures. This tutorial turns a small Flask app into a production-ready service: config, tests, error handling, logging, containers, and health monitoring.
A prototype proves an idea works on your machine. A production service keeps working after you leave: it reads configuration instead of hard-coding, fails gracefully instead of crashing, logs what it did, and can be re-deployed anywhere. The gap between the two is what FDEs spend most of their time crossing.
The eight moves below close the biggest gaps. None is hard alone — together they professionalize any Python service.
Never hard-code secrets or environment-specific values. Pull them from environment variables (and .env in dev).
import os
from dotenv import load_dotenv
load_dotenv() # reads a .env file in dev
HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", "8000"))
API_KEY = os.getenv("API_KEY") # must exist in prod
Configuration now lives outside the code. The same source runs locally and in production simply by changing environment variables — no code edits, no secrets committed to git.
Security note: Never commit
.envor real keys. Add.envto.gitignoreand set real values only in the deployment environment.
Restructure the app so critical logic is a plain module you can test without the server running, then write a test for it.
# app/service.py
def process_total(total: float) -> float:
"""Apply business rules to a total."""
if total < 0:
raise ValueError("total cannot be negative")
return round(total * 1.05, 2) # add 5% handling fee
# tests/test_service.py
from app.service import process_total
def test_positive_total():
assert process_total(100) == 105.0
def test_negative_total_raises():
import pytest
with pytest.raises(ValueError):
process_total(-1)
Moving logic into service.py means you test pure functions instantly with pytest. Tests are what let you refactor confidently — the safety net between "works once" and "works forever".
Catch failures where the user can see them, and log details where only you can. A timestamped format means each log line is attributable and searchable in production.
import logging
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s")
log = logging.getLogger("reports")
app = FastAPI()
@app.exception_handler(Exception)
async def handle_error(request: Request, exc: Exception):
log.exception("Unhandled error: %s", exc) # full traceback, not swallowed
return JSONResponse(status_code=500, content={"error": "something went wrong"})
The two rules: never let the raw exception leak to the user (they get a clean message), but always log the full traceback so you can debug. log.exception captures the stack trace automatically.
Give your deployment a /health endpoint that reports whether the service is alive. Load balancers and monitors ping this constantly.
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
Simple as it is, this endpoint decides whether your container counts as healthy. Add a database check inside when you have one — healthy only if the DB is reachable. (This mirrors the FastAPI style used throughout; return a plain dict and FastAPI serializes it as JSON with status 200.)
A Dockerfile packages your app and everything it needs, so it runs identically anywhere.
FROM python:3.11-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"]
docker build -t fde-reports .
docker run -p 8000:8000 -e API_KEY=secretval --env-file .env fde-reports
python:3.11-slim keeps the image small. Building to a container is what lets the same artifact move from your laptop to staging to the customer's cloud untouched.
Here's the production-minded structure of a small service, with config, health, handling, and a launcher.
import os
import logging
from dotenv import load_dotenv
from fastapi import FastAPI, JSONResponse
load_dotenv()
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s")
log = logging.getLogger("fde-app")
app = FastAPI(title="FDE Platform")
API_KEY = os.getenv("API_KEY")
@app.get("/health")
def health():
return {"status": "ok"}
@app.get("/report")
def report():
# read config, run your pipeline, return a structured result
log.info("report requested")
return {"ok": True, "owner": os.getenv("OWNER", "unknown")}
@app.exception_handler(Exception)
async def handle_error(request, exc):
log.exception("Unhandled error: %s", exc)
return JSONResponse(status_code=500, content={"error": "please retry"})
FROM python:3.11-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"]
Deploy it with Docker, point the port, add /health to your uptime monitor, and this is a platform the customer can rely on.
Save your progress and earn XP for completing tutorials.
4 questions · Pass with 70%+
1Why store configuration and secrets in environment variables?
2What is the purpose of a /health endpoint?
3Why ship the app inside a Docker container?
4Why return a clean error to the user but log the full traceback?
Technology
Forward Deployed Engineer
Lesson group
FDE Projects
Progress
100% complete