Build a rock for a FastAPI app

In this tutorial, we’ll create a simple FastAPI app and learn how to containerise it in a rock with Rockcraft’s fastapi-framework extension.

It should take 25 minutes for you to complete.

Setup

We recommend starting from a clean Ubuntu installation. If we don’t have one available, we can create one using Multipass:

Is Multipass already installed and active? Check by running

snapservicesmultipass

If we see the multipass service but it isn’t "active", then we’ll need to run sudo snap start multipass. On the other hand, if we get an error saying snap "multipass" not found, then we must install Multipass:

sudosnapinstallmultipass

Then we can create the VM with the following command:

multipass launch --memory 2G --disk 10G --name rock-dev 26.04

Finally, once the VM is up, open a shell into it:

multipassshellrock-dev

Unless stated otherwise, we will work entirely within the VM from now on.


LXD will be required for building the rock. Make sure it is installed and initialised:

sudosnapinstalllxd
lxdinit--auto

There is a known connectivity issue with LXD and Docker. To avoid this issue, enable IPv4 forwarding before installing Docker:

echo"net.ipv4.conf.all.forwarding=1"|sudotee-a/etc/sysctl.d/99-forwarding.conf
sudosystemctlrestartsystemd-sysctl

Warning

In a production environment, make sure to use a hardened firewall configuration.

In order to create the rock, we’ll install Rockcraft with classic confinement, which grants it access to the whole file system:

sudosnapinstallrockcraft--classic

We’ll use Docker to run the rock. We can install it as a snap:

sudosnapinstalldocker

By default, Docker is only accessible with root privileges (sudo). We want to be able to use Docker commands as a regular user:

sudoaddgroup--systemdocker
sudoadduser$USERdocker
newgrpdocker

Restart Docker:

sudosnapdisabledocker
sudosnapenabledocker

Note that we’ll also need a text editor. We can either install one of our choice or simply use one of the already existing editors in the Ubuntu environment (like nano).

Finally, create an empty project directory:

mkdirfastapi-hello-world
cdfastapi-hello-world

Create the FastAPI app

Let’s start by creating the "Hello, world" FastAPI app that we’ll use throughout this tutorial.

Create a requirements.txt file, copy the following text into it, and then save it:

~/fastapi-hello-world/requirements.txt
fastapi[standard]>=0.109.1

It’s fastest to test the FastAPI app locally, before we pack it into a rock, so let’s install python3-venv and create a virtual environment we can work in:

sudoapt-getupdate&&sudoapt-getinstallpython3-venv-y
python3-mvenv.venv
source.venv/bin/activate
pipinstall-rrequirements.txt

In the same directory, put the following code into a new file, app.py:

~/fastapi-hello-world/app.py
fromfastapiimport FastAPI
app = FastAPI()
@app.get("/")
async defroot():
 return {"message": "Hello World"}

Run the FastAPI app to verify that it works:

fastapidevapp.py--port8000

Test the FastAPI app by using curl to send a request to the root endpoint. We’ll need a new shell of the VM for this – in a separate terminal, run multipass shell rock-dev again:

curl--faillocalhost:8000

The FastAPI app should respond with {"message":"Hello World"}.

The FastAPI app looks good, so let’s close the terminal instance we used for testing and stop the app in the original terminal instance by pressing Ctrl + C.

Pack the FastAPI app into a rock

Now let’s create a container image for our FastAPI app. We’ll use a rock, which is an OCI-compliant container image based on Ubuntu.

First, we’ll need a rockcraft.yaml project file. We’ll take advantage of a pre-defined extension in Rockcraft with the --profile flag that caters initial rock files for specific web app frameworks. Using the FastAPI profile, Rockcraft automates the creation of rockcraft.yaml and tailors the file for a FastAPI app. From the ~/fastapi-hello-world directory, initialize the rock:

rockcraftinit--profilefastapi-framework

The project file will automatically be created in the project’s working directory as rockcraft.yaml.

Check out the contents of rockcraft.yaml:

catrockcraft.yaml

The top of the file should look similar to the following snippet:

~/fastapi-hello-world/rockcraft.yaml
name:fastapi-hello-world
# see https://documentation.ubuntu.com/rockcraft/latest/explanation/bases/
# for more information about bases and bare bases
base:bare
build-base:ubuntu@24.04
version:'0.1'# just for humans. Semantic versioning is recommended
summary:A summary of your FastAPI app# 79 char long summary
description:|
This is fastapi project's description. You have a paragraph or two to tell the
most important story about it. Keep it under 100 words though,
we live in tweetspace and your description wants to look good in the
container registries out there.
# the platforms this rock should be built on and run on.
# you can check your architecture with `dpkg --print-architecture`
platforms:
amd64:
# arm64:
# ppc64el:
# s390x:

Verify that the name is fastapi-hello-world.

The platforms key must match the architecture of your host. Check the architecture of your system:

dpkg--print-architecture

Edit the platforms key in rockcraft.yaml if required.

Note

For this tutorial, we’ll use the name fastapi-hello-world and assume we’re running on the amd64 platform. Check the architecture of the system using dpkg --print-architecture.

The name, version and platform all influence the name of the generated .rock file.

Pack the rock:

rockcraftpack

Since FastAPI is an experimental extension, ROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS must be enabled.

Once Rockcraft has finished packing the FastAPI rock, we’ll find a new file in the project’s working directory (an OCI archive) with the .rock extension:

ls*.rock-l--block-size=MB

Note

If we changed the name or version in the project file or are not on an amd64 platform, the name of the .rock file will be different.

The size of the rock may vary depending on factors like the architecture we are building on and the packages installed at the time of packing.

Run the FastAPI rock with Docker

We already have the rock as an OCI archive. Now we need to load it into Docker. Docker requires rocks to be imported into the daemon since they can’t be run directly like an executable.

Copy the rock:

rockcraft.skopeocopy\
--insecure-policy\
oci-archive:fastapi-hello-world_0.1_$(dpkg--print-architecture).rock\
docker-daemon:fastapi-hello-world:0.1

This command contains the following pieces:

  • --insecure-policy: adopts a permissive policy that removes the need for a dedicated policy file.

  • oci-archive: specifies the rock we created for our FastAPI app.

  • docker-daemon: specifies the name of the image in the Docker registry.

Check that the image was successfully loaded into Docker:

dockerimagesfastapi-hello-world:0.1

The output should list the FastAPI container image, along with its tag, ID and size:

ubuntu@rock-dev:~/fastapi-hello-world$
sudo docker images fastapi-hello-world:0.1
REPOSITORY TAG IMAGE ID CREATED SIZE
fastapi-hello-world 0.1 30c7e5aed202 2 weeks ago 193MB

Note

The size of the image reported by Docker is the uncompressed size which is larger than the size of the compressed .rock file.

Now we’re finally ready to run the rock and test the containerised FastAPI app:

docker run --rm -d -p 8000:8000 \
 --name fastapi-hello-world fastapi-hello-world:0.1

Use the same curl command as before to send a request to the FastAPI app’s root endpoint which is running inside the container:

curl --fail localhost:8000

The FastAPI app should again respond with {"message":"Hello World"}.

View the app logs

When deploying the FastAPI rock, we can always get the app logs via Pebble:

docker exec fastapi-hello-world pebble logs fastapi

As a result, Pebble will give us the logs for the fastapi service running inside the container. We should expect to see something similar to this:

ubuntu@rock-dev:~/fastapi-hello-world$
sudo docker exec fastapi-hello-world pebble logs fastapi
2024年10月01日T06:32:50.180Z [fastapi] INFO: Started server process [12]
2024年10月01日T06:32:50.181Z [fastapi] INFO: Waiting for application startup.
2024年10月01日T06:32:50.181Z [fastapi] INFO: Application startup complete.
2024年10月01日T06:32:50.182Z [fastapi] INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
2024年10月01日T06:32:58.214Z [fastapi] INFO: 172.17.0.1:55232 - "GET / HTTP/1.1" 200 OK

We can also choose to follow the logs by using the -f option with the pebble logs command above. To stop following the logs, press Ctrl + C.

Cleanup

Now we have a fully functional rock for a FastAPI app! This concludes the first part of this tutorial, so we’ll stop the container and remove the respective image for now:

dockerstopfastapi-hello-world
dockerrmifastapi-hello-world:0.1

Update the FastAPI app

As a final step, let’s update our app. For example, we want to add a new /time endpoint which returns the current time in UTC.

Start by opening the app.py file in a text editor and update the code to look like the following:

~/fastapi-hello-world/app.py
importdatetime
fromfastapiimport FastAPI
app = FastAPI()
@app.get("/")
async defroot():
 return {"message": "Hello World"}
@app.get("/time")
deftime():
 return {"value": f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"}

Since we are creating a new version of the app, open the project file and change the version (e.g. to 0.2). The top of the rockcraft.yaml file should look similar to the following:

~/fastapi-hello-world/rockcraft.yaml
name:fastapi-hello-world
# see https://documentation.ubuntu.com/rockcraft/latest/explanation/bases/
# for more information about bases and bare bases
base:bare
build-base:ubuntu@24.04
version:'0.2'
summary:A summary of your FastAPI app# 79 char long summary
description:|
This is fastapi project's description. You have a paragraph or two to tell the
most important story about it. Keep it under 100 words though,
we live in tweetspace and your description wants to look good in the
container registries out there.
# the platforms this rock should be built on and run on.
# you can check your architecture with `dpkg --print-architecture`
platforms:
amd64:
# arm64:
# ppc64el:
# s390x:

Note

If we repack the rock without changing the version, the new rock will have the same name and overwrite the last one we built. It’s a good practice to change the version whenever we make changes to the app in the image.

Pack and run the rock using similar commands as before:

rockcraft pack
rockcraft.skopeo --insecure-policy \
 copy oci-archive:fastapi-hello-world_0.2_$(dpkg --print-architecture).rock \
 docker-daemon:fastapi-hello-world:0.2
docker images fastapi-hello-world:0.2
docker run --rm -d -p 8000:8000 \
 --name fastapi-hello-world fastapi-hello-world:0.2

Note

Note that the resulting .rock file will now be named differently, as its new version will be part of the filename.

Finally, use curl to send a request to the /time endpoint:

curl --fail localhost:8000/time

The updated app should respond with the current date and time in UTC (e.g. {"value":"2024年10月01日 06:53:54\n"}).

Note

If you are getting a 404 for the /time endpoint, check the Troubleshooting steps below.

Cleanup

We can now stop the container and remove the corresponding image:

dockerstopfastapi-hello-world
dockerrmifastapi-hello-world:0.2

Reset the environment

We’ve reached the end of this tutorial.

If we’d like to reset the working environment, we can simply run the following:

# exit and delete the virtual environment
deactivate
rm-rf.venv__pycache__
# delete all the files created during the tutorial
rmfastapi-hello-world_0.1_$(dpkg--print-architecture).rock\
fastapi-hello-world_0.2_$(dpkg--print-architecture).rock\
rockcraft.yamlapp.pyrequirements.txt

We can also clean the Multipass instance up. Start by exiting it:

exit

And then we can proceed with its deletion:

multipassdeleterock-dev
multipasspurge

Next steps

Congratulations! You’ve reached the end of this tutorial. You created a FastAPI app, packaged it into a rock, and practiced some typical development skills such as viewing logs and updating the app.

But there is a lot more to explore:


Troubleshooting

App updates not taking effect?

Upon changing your FastAPI app and re-packing the rock, if you believe your changes are not taking effect (e.g. the /time endpoint is returning a 404), try running rockcraft clean and pack the rock again with rockcraft pack.