Rust on AWS App Runner - Part 2

Hey hey hey 🍕!

This is the second part of this series, make sure you’ve already read the first part :)

In the first part, we saw how to create a basic Rust API with a GET /pizza endpoint to retrieve the list of pizza.

In this part, let’s see how to containerize our app!

Let’s create our container

Since AWS App Runner is a fully managed container application service, we definitely need a container!

In this section, we will see how to build that container thanks to Docker using a Dockerfile.

A Dockerfile is a text file that contains all the commands needed to build our container, it’s often compared to a recipe.

Rust as the base image

Let’s start with

FROM rust:slim-bullseye
COPY ./ /app
RUN cargo build --release

The first line describes the base image that we want to use, here the official rust one.

This is really convenient since we don’t have to install rust or cargo: both are already installed in this base image.

Then we define the current working directory, here /app

Then we copy our sources (src + Cargo.toml + Cargo.lock) into the container.

Finally we build our API directly inside the container, in release mode, to be sure to have a production-ready binary.

Great, we now have our binary ready in our image!

But wait, we also have rust, cargo and a bunch of useless tools at runtime.

It was convenient to use the official rust image to build, but we don’t need it anymore for the runtime. Fortunately, Docker supports multi stage builds.

Multi-stage builds

We can specify a base image to build and another one to run! That’s exactly what we want!

The idea here is to have the smallest possible image so the cold start would be extremely fast. That’s why we are going to use a distroless base image.

Those images are built by Google and contain almost nothing: no bash, no ssh, no utility tools which makes the image extremely small.

Let’s modify our Dockerfile:

FROM rust:slim-bullseye AS BUILD
COPY ./ /app
RUN cargo build --release

COPY --from=BUILD /app/target/release/app-runner-rust / 
CMD ["./app-runner-rust"]

Here we name our first image BUILD so we can reference that later. Then from the distroless image, we copy our binary from the BUILD image! Super convenient! I love Docker :D

Our runtime image is just the distroless base image + rust binary

Let’s finally build it with docker build . -t maxday/app-runner

Let’s check for our image size: docker images | grep maxday/app-runner

docker images | grep maxday/app-runner
maxday/app-runner                                          latest          e278afc78403   3 minutes ago    20.1MB

20.1MB!!! 🤯 Extremely small!

Woot woot! We now have a ready to deploy container!

Local test

Before deploying, let’s make sure everything looks fine by running it locally and calling our GET /pizza endpoint.

You can run your newly built docker image with:
docker run -d -p 8080:8080 maxday/app-runner

  • -d means detached so the container runs in the background
  • -p 8080:8080 means that you’re bridging the host port 8080 to the container port 8080.

You can check that your container is up and running with:
docker ps

docker ps
CONTAINER ID   IMAGE               COMMAND               CREATED          STATUS          PORTS                    NAMES
e5d8978ee6ee   maxday/app-runner   "./app-runner-rust"   53 seconds ago   Up 53 seconds>8080/tcp   pedantic_kare

Now you can curl the endpoint:
curl http://localhost:8080/pizza

curl localhost:8080/pizza
[{"name":"Margherita","toppings":["tomato","fior di latte"],"price":10},{"name":"Veggie","toppings":["green peppers","onion","mushrooms"],"price":12}]

Awesome! 🎉

Our container is now ready to be used by AWS App Runner.

See you next week for the final episode of this series to deploy it!

Like this content? Consider following me for more!

A question? Feel free to use the comment section ⬇️

You can find me on LinkedIn, YouTube and Twitter!