40
loading...
This website collects cookies to deliver better user experience
TL;DR: We are going to install Docker and create five different containers for a Rust program, each one a little more complex than the other.
$ docker run hello-world
hello-world
image from the Docker Hub and return a text block explaining in detail what happened behind the scene.Dockerfile
that contains the commands that Docker will run to build the image. In a Rust project, it lies alongside the manifest, that is, the Cargo.toml
file;build
we create an image that contains everything we specified in the Dockerfile
. Running an image result in a container. E.g., if our Dockerfile
has instructions to build and run a Web Server, the image will contain the program (that is, the Web Server itself) which will be accessible when we run the image, thus creating the container;There are a few differences between the code you'll find in the guide and the one I am using here:
main.rs
) and library (lib.rs
). That made things harder for Docker (and would make my explanations here too complex) so I just maintained the binary crate and moved the library crate content to a module.This and all other files are available here. You will identify them by their numbers.
holodeck
(so that is the name you will have to change if you're using your project):# 1. This tells docker to use the Rust official image
FROM rust:1.49
# 2. Copy the files in your machine to the Docker image
COPY ./ ./
# Build your program for release
RUN cargo build --release
# Run the binary
CMD ["./target/release/holodeck"]
Dockerfile
is.$ docker build -t holodeck .
docker images
.$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
holodeck latest aad6ff7c3b4d 47 seconds ago 2.42GB
$ docker run -p 8080:3030 --rm --name holodeck1 holodeck
Warp 6, Engage!
-p
maps the port, so what is 3030 inside Docker (the port our warp server is using) will be accessible through 8080 outside, i.e., your machine (if you don't do this, Docker will map a random port);--rm
is here to remove the container after we close it (to visualize this, you may run without --rm
and then run docker ps -a
to list all the containers and then use docker rm containername
to remove it).localhost:8080
.$ curl --location --request POST 'localhost:8080/holodeck' \
--header 'Content-Type: application/json' \
--header 'Content-Type: text/plain' \
--data-raw '{
"id": 2,
"name": "Bride Of Chaotica!"
}'
Simulation #1 created.
$ docker stop holodeck1
holodeck1
-d
:$ docker run -dp 8080:3030 --rm --name holodeck1 holodeck
docker build
, Rust does the entire building process all over again; and the fact we're building for release just makes it worse.# Rust as the base image
FROM rust:1.49
# 1. Create a new empty shell project
RUN USER=root cargo new --bin holodeck
WORKDIR /holodeck
# 2. Copy our manifests
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml
# 3. Build only the dependencies to cache them
RUN cargo build --release
RUN rm src/*.rs
# 4. Now that the dependency is built, copy your source code
COPY ./src ./src
# 5. Build for release.
RUN rm ./target/release/deps/holodeck*
RUN cargo install --path .
CMD ["holodeck"]
I am using install
, but this is the same as build
, except that it places the binary on the indicated path, in this case, the WORKDIR
.
Dockerfile
, build our program (commands 4 and 5), we stop Docker from ignoring the cache. Why does it work? Because every command in our Dockerfile
creates a new layer, which is a modification to the image. When we run docker build
, only the modified layers are updated, the rest is retrieved from the local cache. To put it in practical terms, as long as we don't change the manifest, the dependencies will not have to be rebuilt.main.rs
) took only 33.9 seconds. Great!FROM rust
, which is the build where everything is built, and then called another FROM rust
, copying only the required files from the first build. That allows the final image to retain only these last copied files, therefore decreasing the image size.FROM rust:1.49 as build
# create a new empty shell project
RUN USER=root cargo new --bin holodeck
WORKDIR /holodeck
# copy over your manifests
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml
# this build step will cache your dependencies
RUN cargo build --release
RUN rm src/*.rs
# copy your source tree
COPY ./src ./src
# build for release
RUN rm ./target/release/deps/holodeck*
RUN cargo build --release
# our final base
FROM rust:1.49
# copy the build artifact from the build stage
COPY --from=build /holodeck/target/release/holodeck .
# set the startup command to run your binary
CMD ["./holodeck"]
Dockerfile
, so you already know we can do better.FROM
with:FROM rust:1.49-slim-buster
Some people might wonder why I didn't use Alpine. Well, I did, and buster-slim was 10MB smaller. But the real reason why I avoided Alpine will be clear in the next step.
Again, regarding Alpine. I didn't use it here either for two reasons: