Build a Containerized FastAPI Task API
Build a validated FastAPI task API in Docker and deploy it to Render.
Introduction
30 Second Summary
A task list only helps when every item contains the details you expect. Missing information can quietly turn a useful list into something nobody can trust.
In this project, you will build a typed task API with FastAPI plus Pydantic. You will package it in a Docker container before publishing it through GitHub to a public Render HTTPS URL.
What You'll Build
You open interactive API documentation at a public address where complete tasks receive IDs while incomplete tasks trigger clear validation errors.
By the end of this project, you'll have:
- An interactive task API that you control from browser-based documentation. Each new task returns an incrementing id.
- An enforced request contract that rejects requests without a title. Valid requests receive "done":false by default.
- A local-to-cloud container workflow powered by one Dockerfile. You can use the API at http://127.0.0.1:8000 or through its public onrender.com address.
- Secret Mission: Add a /health endpoint that returns {"status":"ok"}. Push one commit to watch Render deploy the change automatically.
Are there any prerequisites?
You need a 64-bit Windows 10 or Windows 11 computer with 8 GB RAM. You also need hardware virtualization plus a web browser with internet access.
Step 1 guides you through setting up Visual Studio Code, Docker Desktop with WSL 2, GitHub Desktop, GitHub, and Render.
Before We Start
This is your moment to lock in what you are building before the hands-on work begins. One Dockerfile will power your typed FastAPI task API locally through Docker Desktop and publicly through Render.
Set Up Your Windows Container Workspace
Your task API needs a workspace that preserves each change. Visual Studio Code gives you a place to manage the project files.
GitHub Desktop creates the tracked project folder. Docker Desktop supplies the Linux container engine that runs your API.
In this step, get ready to:
- Install Visual Studio Code for your Windows user.
- Create a blank local repository through GitHub Desktop.
- Run Docker Desktop with its WSL 2 Linux backend.
Install Visual Studio Code
The Windows User setup installs Visual Studio Code for your account without requiring administrator access. Restarting PowerShell makes the code command available.
- Visit the official Visual Studio Code Windows setup page.
- Download the Windows User setup.
- Run the downloaded installer.
- Complete the installation with the default options.
Why use the User setup?
The User setup installs Visual Studio Code for your Windows account. It also adds the editor command to your PATH.
- Close every PowerShell window that was open during the installation.
- Press the Windows key to open Windows Search.
- Type PowerShell into Windows Search.
- Press Enter to open PowerShell.
- Launch Visual Studio Code by running this command:
code
What does this command prove?
The code command uses the PATH entry added by the installer. You will see Visual Studio Code open when PowerShell can find that entry.
Visual Studio Code did not open?
Confirm that you installed the Windows User setup. Close every PowerShell window before trying the command again.
Rerun the installer if PowerShell still cannot find the command.
Help me fix the Visual Studio Code command on Windows.
Create the local Git repository
A Git repository records the history of your project. Your GitHub account connects that local history to the publishing workflow you use later.
- Visit the official GitHub Desktop installation page.
- Select Download for Windows.
- Run the downloaded setup file.
- Press the Windows key to open Windows Search.
- Type GitHub Desktop into Windows Search.
- Press Enter to open GitHub Desktop.
- Create a GitHub account in your browser if you do not have one.
- Use GitHub Desktop's sign-in option for GitHub.com.
Your browser handles the authorization request. GitHub Desktop displays your account when the sign-in finishes.
- Approve the GitHub Desktop authorization request in your browser.
- Return to GitHub Desktop after authorization finishes.
- Select Create a New Repository on your Hard Drive.
- Enter containerized-task-api in the Name field.
- Choose your Desktop in the Local path field.
- Keep the README option disabled.
- Keep the generated ignore-file option disabled.
- Keep the license option disabled.
- Select Create repository.
What did GitHub Desktop create?
GitHub Desktop creates the containerized-task-api folder on your Desktop. It initializes Git history inside that folder.
The repository contains no application source files. This clean starting point keeps the setup step focused on your workspace.
- Switch back to Visual Studio Code from earlier.
- Select File from the top menu.
- Select Open Folder....
- Choose the containerized-task-api folder from your Desktop.
- Select Select Folder.
- Select Yes, I trust the authors if Visual Studio Code displays the Workspace Trust prompt.
You will see containerized-task-api at the top of the Explorer view. The folder has no application source files yet.
Repository missing or opened incorrectly?
Return to GitHub Desktop. Confirm that the current repository is named containerized-task-api.
Use the repository path shown in GitHub Desktop when Visual Studio Code asks you to choose a folder.
Help me find my GitHub Desktop repository on Windows.
Start the Linux container engine
Docker Desktop uses Windows Subsystem for Linux 2 to run Linux containers on Windows. Docker Desktop requires WSL version 2.1.5 or later for this backend.
This check reaches into Windows system features. The administrator permission request is expected.
- Press the Windows key to open Windows Search.
- Type PowerShell into Windows Search.
- Use the administrator option for the PowerShell search result.
- Approve the Windows permission request.
- Check the installed WSL version by running this command:
wsl --version
What does this command check?
The command reports the installed WSL components. Its version tells you whether the Docker Desktop Linux backend can use the current installation.
✔️ WSL meets the minimum version
Your WSL version is 2.1.5 or later. Continue to the confirmation check below.
ⓧ WSL is older than required
Your installed WSL components need an update before Docker Desktop can use this backend.
- Update WSL from the elevated PowerShell window by running this command:
wsl --update
What does this command change?
The command updates the installed WSL components. This prepares the backend used by Docker Desktop.
- Restart Windows if the update requests it.
ⓧ PowerShell cannot find WSL
Windows needs the WSL components before Docker Desktop can run its Linux container engine.
- Install WSL from the elevated PowerShell window by running this command:
wsl --install
What does this command install?
The command enables the Windows features needed for WSL. It installs the components required to run a Linux environment.
- Restart Windows if the installation requests it.
- Return to the elevated PowerShell window.
- Reopen elevated PowerShell if Windows restarted.
- Confirm the WSL installation by running this command:
wsl --version
What should you see?
PowerShell reports WSL version 2.1.5 or later. Your Windows computer is ready for Docker Desktop's WSL 2 backend.
WSL still does not meet the requirement?
Confirm that PowerShell has administrator permissions. Apply pending Windows updates before retrying the WSL update.
Help me prepare WSL for Docker Desktop.
- Visit the official Docker Desktop Windows installation page.
- Download the Docker Desktop installer for Windows.
- Run the downloaded installer.
- Use the recommended per-user installation mode.
- Keep the WSL 2 backend enabled.
- Complete the installation.
Why use the WSL 2 backend?
WSL 2 lets Docker Desktop run Linux containers on your Windows computer. Your local image can use the same operating-system family as the cloud deployment.
Your personal learning use qualifies for Docker Desktop's free-use terms.
- Press the Windows key to open Windows Search.
- Type Docker Desktop into Windows Search.
- Press Enter to start Docker Desktop.
- Accept the Docker Desktop terms.
- Keep Docker Desktop open until its Linux container engine reports that it is running.
Before the final check, do you expect PowerShell to reach both parts of Docker?
- Return to the PowerShell window from earlier.
- Check the Docker connection by running this command:
docker version
Your container engine is ready
- The Client section confirms that PowerShell can run Docker commands.
- The Server section confirms that Docker Desktop can receive those commands.
That completes the workspace setup. Your Windows computer can now run Linux containers from the tracked project folder.
Final check missing a section?
Keep Docker Desktop open. Restart PowerShell before running the check again.
If only the client appears, inspect Docker Desktop for a WSL backend warning. Complete any requested Windows restart.
Help me complete the Docker connection check.
✔️ Awesome, I've got everything!
Your repository is open in Visual Studio Code. Docker Desktop can receive commands through its WSL 2 Linux engine.
ⓧ I'd like to double check the final state
- Confirm GitHub Desktop is signed in to your GitHub account.
- Confirm GitHub Desktop shows the containerized-task-api repository.
- Confirm Visual Studio Code has the containerized-task-api folder open.
- Confirm the repository contains no application source files.
- Confirm Docker Desktop is running with the WSL 2 backend.
- Confirm the Docker check shows the Client section.
- Confirm the Docker check shows the Server section.
Your Windows container workspace is ready. Next, you will build the first task API image and open its interactive documentation in your browser.
Launch the First Task API Container
Your Windows workspace can now build Linux containers through Docker Desktop. The next goal is to make your task API answer from inside one.
FastAPI gives your service interactive documentation for testing each route. A container image packages the application with its runtime so the same bundle can run elsewhere.
In this step, get ready to:
- Create a FastAPI service with task creation and listing routes.
- Package the application files in a Docker image.
- Run the container and create your first task through the interactive documentation.
Create the Task API
Each REST endpoint connects an HTTP method with a Python function. Your first version keeps every task in an in-memory list.
The POST route reads JSON as a plain dictionary. It assigns an incrementing identifier before storing the task.
- Select Explorer in the Visual Studio Code Activity Bar.
- Select New File... in the Explorer toolbar.
You will see a filename field inside the open containerized-task-api repository.
- Enter main.py in the filename field.
- Press Enter.
The new main.py file opens in the editor.
- Add the first API version by pasting this code into main.py:
from fastapi import FastAPI
app = FastAPI()
tasks: list[dict[str, object]] = []
@app.get("/")
def read_root():
return {"message": "Containerized Task API"}
@app.get("/tasks")
def list_tasks():
return tasks
@app.post("/tasks")
def create_task(task: dict):
task_record: dict[str, object] = {
"id": len(tasks) + 1,
**task,
}
tasks.append(task_record)
return task_record
What Does This Code Do?
- The app instance connects the route decorators to FastAPI.
- The tasks list holds task records while the application process is running.
- The read_root() function returns a short service message.
- The list_tasks() function returns every stored task.
- The create_task() function adds an incrementing id to the submitted dictionary.
- Save main.py by pressing Ctrl+S.
- Check that the unsaved-change dot disappears from the main.py editor tab.
Seeing Python Warnings?
Confirm the file is named main.py with the .py extension. Package import warnings can remain until Docker installs the dependencies during the image build.
Help me check my first FastAPI source file.
Package the API for Docker
The dependency file pins the exact FastAPI release used by the application. It also pins Pydantic so the image installs the expected request-model library.
- Select New File... in the Explorer toolbar.
A new filename field opens below the containerized-task-api folder.
- Enter requirements.txt in the filename field.
- Press Enter.
The blank requirements.txt file opens in the editor.
- Pin the project dependencies by pasting these lines into requirements.txt:
fastapi[standard]==0.161.1
pydantic==2.14.1
Why Pin These Dependencies?
- The fastapi[standard] package at version 0.161.1 includes the FastAPI command-line tools used by the container.
- Pydantic version 2.14.1 provides the request-model features used later in the project.
- Exact pins make future image builds install the same dependency versions.
- Save requirements.txt by pressing Ctrl+S.
- Check that requirements.txt remains visible in the Explorer.
Dependency File Looks Different?
Confirm each dependency has its own line. Check that both version pins contain two equals signs.
Help me check my dependency pins.
The Dockerfile describes how Docker builds the image. Copying dependencies before application code lets Docker reuse the dependency layer when only main.py changes.
- Select New File... in the Explorer toolbar.
The filename field opens in the same repository.
- Enter Dockerfile without a file extension.
- Press Enter.
The blank Dockerfile opens in the editor.
- Define the image build by pasting this content into Dockerfile:
FROM python:3.14
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./main.py /code/main.py
CMD ["fastapi", "run", "main.py", "--proxy-headers", "--port", "10000"]
How Does the Image Come Together?
- The python:3.14 base image supplies the Linux Python runtime.
- The WORKDIR /code instruction sets the application directory inside the image.
- The first COPY instruction adds the dependency file before the source code.
- The RUN instruction installs the pinned packages.
- The final CMD instruction starts FastAPI on port 10000 with proxy-header support.
- Save Dockerfile by pressing Ctrl+S.
- Check that the Explorer lists Dockerfile without a file extension.
Dockerfile Not Recognized?
Confirm the filename is exactly Dockerfile. Remove any .txt extension added to the name.
Help me inspect my Dockerfile structure.
A .dockerignore file keeps local caches and repository metadata out of the build context. This reduces irrelevant files sent to Docker.
- Select New File... in the Explorer toolbar.
The filename field opens for the final packaging file.
- Enter .dockerignore in the filename field.
- Press Enter.
The blank .dockerignore file opens in the editor.
- Exclude local development files by pasting these patterns into .dockerignore:
.git/
.venv/
__pycache__/
*.py[cod]
What Stays Out of the Image Build?
- The .git/ pattern excludes local Git metadata.
- The .venv/ pattern excludes local virtual environments.
- The __pycache__/ pattern excludes Python cache directories.
- The *.py[cod] pattern excludes compiled Python bytecode files.
- Save .dockerignore by pressing Ctrl+S.
- Check that the Explorer lists all four project files.
Missing the Docker Ignore File?
Confirm the filename starts with a dot. Check that it has no extra file extension.
Help me verify my Docker build files.
✔️ Awesome, I've got everything!
Your four project files are saved inside containerized-task-api. You are ready to build the image.
ⓧ I'd like to double check the full code
Compare your saved files with these complete references.
File: main.py.
from fastapi import FastAPI
app = FastAPI()
tasks: list[dict[str, object]] = []
@app.get("/")
def read_root():
return {"message": "Containerized Task API"}
@app.get("/tasks")
def list_tasks():
return tasks
@app.post("/tasks")
def create_task(task: dict):
task_record: dict[str, object] = {
"id": len(tasks) + 1,
**task,
}
tasks.append(task_record)
return task_record
File: requirements.txt.
fastapi[standard]==0.161.1
pydantic==2.14.1
File: Dockerfile.
FROM python:3.14
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./main.py /code/main.py
CMD ["fastapi", "run", "main.py", "--proxy-headers", "--port", "10000"]
File: .dockerignore.
.git/
.venv/
__pycache__/
*.py[cod]
Build and Run the Container
Docker now has every instruction needed to assemble the API image. Port publishing connects a port on Windows to the FastAPI port inside the Linux container.
- Select View from the Visual Studio Code top menu.
- Select Terminal.
The integrated terminal opens in the containerized-task-api repository.
The first image build can take a few minutes while Docker downloads the Python base layer.
- Build the containerized-task-api image by running this command:
docker build -t containerized-task-api .
What Does This Command Build?
- The docker build command reads the Dockerfile from the current repository.
- The -t option names the image containerized-task-api.
- The final dot sends the current repository as the build context.
The build finishes when every Dockerfile instruction completes. You will return to the terminal prompt without a failed build step.
Image Build Failed?
Confirm Docker Desktop from earlier is still running. Check that the terminal is inside the containerized-task-api repository.
Help me diagnose my Docker image build.
Before you start the container, do you expect Windows port 8000 to reach FastAPI on container port 10000?
- Start the container in the foreground by running this command:
docker run --rm --name containerized-task-api -p 127.0.0.1:8000:10000 containerized-task-api
How Does the Container Reach Your Browser?
- The --rm option removes the container after it stops.
- The --name option assigns the name containerized-task-api.
- The -p 127.0.0.1:8000:10000 option publishes the service only to your local computer.
- The final image name tells Docker which image to run.
You will see FastAPI startup logs in the terminal. The terminal remains occupied while the container serves requests.
Container Did Not Start?
Confirm the image build completed before this command. A port conflict means another process is already using local port 8000.
Help me diagnose why my API container will not start.
Before you load the documentation, which three routes do you expect FastAPI to show?
- Enter http://127.0.0.1:8000/docs in your web browser address bar.
- Press Enter.
You will see the interactive API page with GET /, GET /tasks, and POST /tasks.
- Select POST /tasks to expand the operation.
- Select Try it out.
- Replace the request body with {"title":"Learn containers","done":false}.
- Select Execute.
The response body shows an id of 1. It includes the title Learn containers.
The done value is false. Your first task has travelled from the browser into the API running inside the container.
Interactive Documentation Not Loading?
Confirm the container terminal still shows a running process. Check that the browser address uses local port 8000 with the /docs path.
Help me reach my containerized FastAPI documentation.
Your task API now runs from a locally built Linux container. Next, you will test how much the plain dictionary request body actually guarantees.
Break the API Contract on Purpose
Your FastAPI service is already running inside a container. It creates valid tasks through its interactive documentation.
Now you need to discover what happens when a request leaves out the most important field. The current dict body accepts arbitrary object shapes, so this test exposes a gap in the API contract.
In this step, get ready to:
- Inspect the request shape advertised by POST /tasks.
- Submit an incomplete task to the running API.
- Prove the incomplete task entered the in-memory list.
Inspect the advertised request shape
A request schema defines the allowed body shape. Required fields appear as part of that contract.
- Switch back to the browser tab showing http://127.0.0.1:8000/docs from earlier.
- Expand POST /tasks in the interactive documentation.
- Inspect the request body description inside the expanded endpoint.
You will see a generic object body. The documentation does not identify title as a required field.
Why is the body an arbitrary object?
The task: dict parameter allows keys whose names are unknown in advance. FastAPI therefore has no declared task fields to display or enforce.
Submit a task without a title
A permissive contract becomes a real problem when incomplete data reaches the application. You are about to test whether this endpoint protects the task list.
- Activate the request editor inside the expanded POST /tasks endpoint.
- Replace the request body with {"done":false}.
Before you send the request, do you think the API accepts it or rejects it?
- Send the request from the expanded POST /tasks endpoint.
The response includes an id plus done. It has no title.
Why was the incomplete task accepted?
The endpoint receives the body as a plain dict. A dictionary does not declare title as required.
The **task expression copies every received key into task_record. When the request omits title, the stored record omits it too.
- Expand GET /tasks in the interactive documentation.
Before you list the tasks, do you expect the incomplete object to remain in memory?
- Send the request from the expanded GET /tasks endpoint.
The response list includes the incomplete task. Its stored object has an id plus done, but no title.
You found the contract gap in action: the API stored a task that has no title.
Do not see the incomplete task?
- Confirm the foreground container from earlier is still running.
- Submit {"done":false} through POST /tasks again.
- Run GET /tasks in the same container session.
A container restart clears this in-memory task list. Help me trace why my incomplete task is missing from the FastAPI task list.
Your running API has now exposed the missing contract. Next, you will give task requests a typed shape that blocks missing titles.
Enforce the Task Schema
You have already watched an incomplete task enter your FastAPI service. The plain dictionary gives every JSON object a path into the in-memory task list.
A Pydantic request model defines the required fields before create_task() runs. This step closes the missing-title gap you reproduced.
In this step, get ready to:
- Define the TaskCreate request model.
- Update create_task() to store validated fields.
- Verify the rebuilt API contract across both request cases.
Define the request model
The TaskCreate model gives each request a known shape. Its required title field blocks incomplete tasks before they reach your storage logic.
Stopping the foreground container clears only its temporary task data. Your project files remain safe in the repository.
- Switch back to the PowerShell terminal where the foreground container is running.
- Press Ctrl+C to stop the container.
The incomplete task from the previous step is now cleared from memory. The restarted API begins with a fresh tasks list.
- Replace the opening section of main.py through the tasks declaration by copying this code:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
tasks: list[dict[str, object]] = []
class TaskCreate(BaseModel):
title: str
done: bool = False
What does the request model enforce?
- BaseModel provides the validation behavior used by TaskCreate.
- title: str requires every accepted request to contain a string title.
- done: bool = False supplies False when the request omits the completion state.
- Save main.py.
- Check that the model compiles inside the image by rebuilding it with this command:
docker build -t containerized-task-api .
What does this build check?
The build copies the updated main.py into a new image tagged containerized-task-api. A completed build confirms that Python can load the new import and model declaration.
Good progress. Your request model now compiles inside the same Linux image that runs the API.
Did the image fail to build?
Confirm that BaseModel uses the same capitalization in the import and class declaration. Check that both field lines are indented inside TaskCreate.
Compare the opening section of main.py with the code above. Help me diagnose why my Docker image stopped building after I added the TaskCreate Pydantic model.
Update the task creation logic
The endpoint now needs to accept TaskCreate instead of an arbitrary dictionary. Explicit field access ensures that every stored task has the validated shape.
- Replace the existing create_task() function in main.py with this version:
@app.post("/tasks")
def create_task(task: TaskCreate):
task_record: dict[str, object] = {
"id": len(tasks) + 1,
"title": task.title,
"done": task.done,
}
tasks.append(task_record)
return task_record
How does task creation change?
- task: TaskCreate tells FastAPI to validate the incoming request against the model.
- task.title reads the required title from the validated request.
- task.done reads the supplied completion state or its default value.
- len(tasks) + 1 keeps the incrementing identifier behavior from the earlier version.
- Save main.py.
✔️ Awesome, I've got everything!
Your main.py now defines the request model and stores its validated fields explicitly.
ⓧ I'd like to double check the full code
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
tasks: list[dict[str, object]] = []
class TaskCreate(BaseModel):
title: str
done: bool = False
@app.get("/")
def read_root():
return {"message": "Containerized Task API"}
@app.get("/tasks")
def list_tasks():
return tasks
@app.post("/tasks")
def create_task(task: TaskCreate):
task_record: dict[str, object] = {
"id": len(tasks) + 1,
"title": task.title,
"done": task.done,
}
tasks.append(task_record)
return task_record
How does the full file fit together?
The file now places TaskCreate before the route functions that use it. The updated POST endpoint turns validated model fields into a stored task record.
Rebuild and verify the contract
A Docker image keeps the source code that existed when you built it. Rebuilding packages the finished endpoint before you start the replacement container.
- Package the completed API changes by rebuilding the image with this command:
docker build -t containerized-task-api .
What does the rebuild include?
The image now contains the BaseModel import and the TaskCreate request schema. It also contains the updated create_task() endpoint.
The image should build successfully. The finished validation logic is now packaged for the next container.
Is the final rebuild failing?
Confirm that every line in create_task() matches the full-file reference. A missing comma or incorrect indentation can stop Python from loading the endpoint.
Help me find the syntax or model error in my updated FastAPI task endpoint.
- Start the validated API in a foreground container by running this command:
docker run --rm --name containerized-task-api -p 127.0.0.1:8000:10000 containerized-task-api
How does the container run?
- --rm removes the container after it stops.
- --name containerized-task-api assigns the container its project name.
- -p 127.0.0.1:8000:10000 publishes the API on local port 8000 while the container listens on port 10000.
The terminal stays attached while the API serves requests. The restarted process begins with an empty in-memory tasks list.
Did the container fail to start?
Confirm that Docker Desktop is still running. Check that the previous foreground container stopped before you reused the name and port.
Help me diagnose why my rebuilt containerized-task-api image will not start on local port 8000.
- Switch back to the browser tab at http://127.0.0.1:8000/docs.
- Refresh the page.
The POST request body now reflects TaskCreate. The model exposes title as a required field.
Before you test the rebuilt API, which request should TaskCreate reject?
- Expand POST /tasks in the interactive docs.
- Submit {"done":false} with the interactive request controls.
The response identifies title as missing. The incomplete task never enters the in-memory list.
- Submit {"title":"Deploy the API"} through the same endpoint.
The response includes an id and the title Deploy the API. It also includes "done":false from the model default.
You closed the contract gap. The container now rejects incomplete tasks while valid requests receive a consistent stored shape.
Your typed API is ready for a wider audience. Next, you will publish the repository and deploy the same Dockerfile to a public HTTPS URL.
Publish and Deploy on Render
Your typed FastAPI service now rejects incomplete tasks inside its local Docker container. That proves the application contract works before it leaves your computer.
A local container proves portability on one machine. Publishing the repository to GitHub gives Render the source it needs to rebuild the same Dockerfile at a public HTTPS URL.
In this step, get ready to:
- Save the finished API as a local Git commit.
- Publish the committed repository to GitHub.
- Prove the Render deployment through its public API documentation.
Publish the repository to GitHub
A Git commit captures the exact files that produced your validated local container. Publishing that commit gives the cloud deployment a traceable source.
- Return to GitHub Desktop from earlier.
- Review the changed project files in the repository.
- Enter Build containerized task API in the Summary field.
- Click Commit to main.
- Click Publish repository.
- Confirm the repository name remains containerized-task-api.
- Choose your preferred visibility with Keep this code private.
- Click Publish Repository.
That is your first cloud-ready handoff complete. GitHub Desktop now links the local containerized-task-api repository to the published copy under your GitHub account.
Repository not publishing?
Confirm GitHub Desktop is signed in to the GitHub account where you want the repository to live. Repository access or a dropped internet connection can interrupt publishing.
Ask for help with the account state you see: Help me diagnose why GitHub Desktop cannot publish my local containerized-task-api repository.
Connect Render to GitHub
Render uses your GitHub connection to read the repository. That connection also gives the service a source branch for future deployments.
GitHub opens an authorization screen during sign-up. The screen asks you to approve repository access for Render.
- Open the Render website in your browser.
- Choose the GitHub sign-up option.
- Authorize Render to access your GitHub account.
- Select New in the Render Dashboard.
- Select Web Service.
- Select the containerized-task-api GitHub repository.
- Set Language to Docker.
- Select the Free compute plan.
Deploy and test the public API
Stay on the Free plan
The Render Free compute plan costs $0/month. Usage limits still apply to the workspace.
This project stays on the prescribed personal learning path. Keep the Free plan selected before creating the service.
The first cloud build needs time to install every dependency. A few quiet minutes on the deployment screen can be normal.
- Leave the Dockerfile location set to the repository root.
- Leave the command override unset.
- Click Create Web Service.
- Watch the Deploys page until the build succeeds.
Render has now built the repository-root Dockerfile. The resulting web service runs the same container entry point that you tested locally.
Why deploy the Dockerfile?
Render builds an image directly from the Dockerfile in your repository. This keeps the application runtime consistent between your Windows computer and the cloud service.
Render terminates incoming TLS before forwarding traffic to the container. The --proxy-headers flag helps FastAPI interpret those forwarded request details.
- Record the generated onrender.com HTTPS URL here: your Render onrender.com URL.
- Open your Render onrender.com URL with /docs appended in your browser.
Before you send the public request, do you expect the same valid body to behave like it did in the local container?
- Execute POST /tasks with {"title":"Live on Render"} from the interactive documentation.
You will see a successful public response containing an id. The response also contains the title "Live on Render" with done set to false.
Public request not working?
If the page is slow after an idle period, allow about one minute for the Free web service to start. If the deployment failed, confirm the Dockerfile remains at the repository root.
Get help with the deployment state you see: Help me diagnose why my Render Docker deployment or public FastAPI request is failing.
Why can task data disappear?
The tasks list lives in the running process memory. A restart or redeployment starts the API with a fresh list.
Render Free web services also spin down after 15 minutes without inbound traffic. This API demonstrates deployment without adding persistent storage.
You have shipped the same validated API from one Dockerfile to two environments. Your public Render service now creates typed tasks through an HTTPS endpoint.
✔️ Awesome, I've got everything!
Your repository is published to GitHub. Render has built its root Dockerfile and served the validated task API from your public URL.
- Confirm the local commit is named Build containerized task API.
- Confirm the GitHub repository is named containerized-task-api.
- Confirm the Render service uses the Free Docker configuration.
- Confirm the public /docs page creates a task with its generated identifier.
ⓧ I'd like to double check the full code
Compare each local file with these unchanged deployment sources. These are the exact files committed to GitHub and built by Render.
.git/
.venv/
__pycache__/
*.py[cod]
What does this file protect?
The ignore rules keep Git metadata, virtual environments, caches, and Python bytecode out of the Docker build context.
FROM python:3.14
WORKDIR /code
COPY ./requirements.txt /code/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
COPY ./main.py /code/main.py
CMD ["fastapi", "run", "main.py", "--proxy-headers", "--port", "10000"]
What does this file package?
The Dockerfile installs the pinned dependencies before copying the application. Its final command starts FastAPI on the port expected by Render.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
tasks: list[dict[str, object]] = []
class TaskCreate(BaseModel):
title: str
done: bool = False
@app.get("/")
def read_root():
return {"message": "Containerized Task API"}
@app.get("/tasks")
def list_tasks():
return tasks
@app.post("/tasks")
def create_task(task: TaskCreate):
task_record: dict[str, object] = {
"id": len(tasks) + 1,
"title": task.title,
"done": task.done,
}
tasks.append(task_record)
return task_record
What does this file serve?
The application defines the root route and the two task routes. Its request model requires a title while giving new tasks a default incomplete state.
fastapi[standard]==0.161.1
pydantic==2.14.1
What does this file pin?
The dependency file fixes the FastAPI and Pydantic versions used by every image build. Render installs the same package versions as your local image.
Secret mission
Ship a Health Check Through Continuous Deployment
Add a public health route to the existing API. Push one commit so Render deploys the change automatically.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep your resources available, suspend the public service for later, or remove the learning artifacts. Render Free compute costs $0 per month within included limits.
Cost warning
Render Free services share 750 Free instance hours per workspace each calendar month. An idle service spins down after 15 minutes without inbound traffic.
A payment method can make supplementary usage billable. Without one, exceeding certain included limits can suspend services or disable new builds.
The service filesystem is ephemeral. Your in-memory task list can disappear after a redeploy, restart, or spin-down.
Resources you used:
- A local Docker image named containerized-task-api.
- A local Git repository folder named containerized-task-api.
- A GitHub repository named containerized-task-api.
- A Render Free Docker web service connected to the GitHub repository.
Keep everything running
No action is needed. Choose this if you want to keep improving or demonstrating the API.
- Keep the local Docker image for future container runs.
- Keep the local containerized-task-api repository folder for future code changes.
- Keep the GitHub repository connected to Render for automatic deployments.
- Leave the Render web service deployed at its public HTTPS URL.
Render spins the Free service down after 15 minutes without inbound traffic. That first request can take about one minute while the service starts again.
Your task records live in process memory. A restart, redeploy, or spin-down can clear them.
Pause - I'll come back to this later
Suspend the public service to stop requests while preserving its deployment configuration. Your local image remains available.
- Return to the Render Dashboard.
- Select the checkbox for your Docker web service.
- Choose Suspend from the bulk action menu.
The public URL stops serving your API until you resume the service. The same bulk action flow provides Resume when you return.
Delete - I don't want to use this again
Deleting the local folder is permanent. Deleting the GitHub repository is permanent too.
Render's current public documentation does not publish an individual web-service deletion control. This cleanup path suspends that service through the verified dashboard controls.
- Use GitHub Desktop to reveal the local containerized-task-api repository in File Explorer.
- Keep that File Explorer window available for the final local cleanup.
- Return to the Render Dashboard.
- Select the checkbox for your Docker web service.
- Choose Suspend from the bulk action menu.
Your public API is now suspended. The service configuration stays in your Render workspace.
- Return to the containerized-task-api repository on GitHub.
- Select Settings in the repository navigation.
- Select General in the settings sidebar.
- Scroll to Danger Zone.
- Select Delete this repository.
- Complete the confirmation controls to remove the repository.
That is the remote copy cleaned up. Your local folder remains available for the final cleanup checks.
- Start a PowerShell terminal in Visual Studio Code for the open containerized-task-api folder.
- Remove the local Docker image by running this command:
docker image rm containerized-task-api
What does this command do?
The docker image rm command removes or untags the local image named containerized-task-api. The command only targets the local image.
- Switch back to Docker Desktop.
- Confirm that containerized-task-api is absent from your local image list.
The local image is gone. Docker no longer stores that project build on your computer.
Image still in use?
- Stop any container that still uses containerized-task-api in Docker Desktop.
- Run the image removal command again.
- Help me remove the local Docker image.
The last deletion removes your source folder permanently. Continue only when you are finished with this project.
- Close Visual Studio Code.
- Close GitHub Desktop.
- Return to the File Explorer window showing containerized-task-api.
- Move to the parent folder that contains containerized-task-api.
- Delete the containerized-task-api folder from File Explorer.
You've cleared every learning artifact with a verified removal path. The remaining Render web service is suspended under its documented dashboard controls.
Nice Work!
Nice Work!
You did it! Your typed FastAPI task API now runs locally in a Docker container from the same Dockerfile deployed by Render.
What you learned:
- Built a task API with POST /tasks for creating in-memory tasks. Added GET /tasks for listing the current process state.
- Exposed the missing-title failure by sending an incomplete request. Enforced schema validation with Pydantic so incomplete requests are rejected.
- Packaged the service as a Linux container image from the root Dockerfile. Published the repository to GitHub. Deployed the same container definition to a public onrender.com HTTPS URL.
- Secret Mission: Added GET /health as a liveness check. Pushed the Add health endpoint commit. Confirmed Render's automatic deployment through the public /health response.
Ready to quiz yourself?