» Rust: Build a REST API with Rocket » 3. Deployment » 3.3 Docker and Docker Compose

Docker and Docker Compose

Docker allows developers to package their applications along with all dependencies into a single unit called a container. This ensures consistency across different environments, such as development, testing, and production, reducing the "it works on my machine" problem.

Install Docker: https://docs.docker.com/engine/install/

Dockerfile

A Dockerfile is a text file that contains instructions for building a Docker image. It defines the steps needed to create a Docker image, which serves as a blueprint for launching Docker containers.

Add Dockerfile:

# Use a Rust Docker image as the base
FROM rust:1.76-alpine3.19 as builder

# Set the working directory inside the container
WORKDIR /app

# Install necessary packages
RUN apk update && \
    apk add --no-cache musl-dev pkgconfig openssl-dev

# Copy the Rust project files to the container
COPY Cargo.toml .
COPY src/ /app/src

# Define a build argument with a default value
ARG BINARY_NAME=lrbooks

# Build the Rust project
# See: https://github.com/rust-lang/rust/issues/115430
RUN RUSTFLAGS="-Ctarget-feature=-crt-static" cargo build --release --bin ${BINARY_NAME}

# Start a new stage from Alpine Linux
FROM alpine:3.19

# Install required packages
RUN apk update && \
    apk add --no-cache libgcc

# Define an environment variable from the build argument
ENV BINARY_NAME=lrbooks

# Set the working directory inside the container
WORKDIR /app

# Copy the built binary from the previous stage to the current stage
COPY --from=builder /app/target/release/${BINARY_NAME} .

# Command to run the binary when the container starts
CMD ./${BINARY_NAME}

Alpine Linux is a lightweight and secure Linux distribution that is particularly well-suited for containerized environments, embedded systems, and resource-constrained environments where efficiency and security are paramount.

Build your docker image like this:

docker build . -t lrbooks-rust:latest

Note:
Use sudo docker ... in case you have any permission issue.

Do docker images to check your images:

docker images

Result:

REPOSITORY                 TAG       IMAGE ID       CREATED          SIZE
lrbooks-rust               latest    065660b9adf3   15 seconds ago   32.4MB
...

Docker Compose

Docker Compose is a tool for defining and running multi-container Docker applications. It allows you to use a YAML file to configure your application's services, networks, and volumes, and then spin up all the containers required to run your application with a single command.

Note:
The easiest and recommended way to get Docker Compose is to install Docker Desktop. Docker Desktop includes Docker Compose along with Docker Engine and Docker CLI which are Compose prerequisites.

Install Compose if needed: https://docs.docker.com/compose/install/

Add compose/docker-compose.yml:

services:
  lr-rest-books:
    image: lrbooks-rust:latest
    ports:
      - 8000:8000
    volumes:
      - ./config.toml:/app/config.toml
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started
      mongo:
        condition: service_started
  redis:
    image: docker.io/bitnami/redis:7.0
    environment:
      - REDIS_PASSWORD=${REDIS_PASSWORD}
    ports:
      - 6379:6379
  mysql:
    image: docker.io/bitnami/mysql:5.7.43
    environment:
      - MYSQL_DATABASE=lr_book
      - MYSQL_USER=test_user
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
    ports:
      - 3306:3306
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10
    volumes:
      - ~/lr-mysql-data:/bitnami/mysql/data
  mongo:
    image: bitnami/mongodb:latest
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    ports:
      - 27017:27017
    volumes:
      - ~/lr-mongodb-data:/bitnami/mongodb

Add compose/config.toml:

[app]
port = 8000
page_size = 5
token_secret = "I_Love_LiteRank"
token_hours = 48

[db]
file_name = "test.db"
dsn = "mysql://test_user:test_pass@mysql:3306/lr_book"
mongo_uri = "mongodb://mongo:27017"
mongo_db_name = "lr_book"

[cache]
redis_uri = "redis://:test_pass@redis:6379/0"

Add compose/.env:

REDIS_PASSWORD=test_pass
MYSQL_PASSWORD=test_pass
MYSQL_ROOT_PASSWORD=test_root_pass

Caution: .env files should be ignored in .gitignore.

Changes in .gitignore:

@@ -22,3 +22,4 @@ go.work
 
 test.db
 .vscode
+.env

Run it:

cd compose
docker compose up

You should see something like this:

[+] Running 4/4
 ✔ Container compose-mongo-1          Created                                                                                                                                                                                          0.0s 
 ✔ Container compose-redis-1          Created                                                                                                                                                                                          0.0s 
 ✔ Container compose-mysql-1          Recreated                                                                                                                                                                                        0.1s 
 ✔ Container compose-lr-rest-books-1  Recreated                                                                                                                                                                                        0.0s 
Attaching to lr-rest-books-1, mongo-1, mysql-1, redis-1
redis-1          | redis 13:24:52.38 
redis-1          | redis 13:24:52.39 Welcome to the Bitnami redis container
...

mongo-1          | mongodb 13:24:52.60 INFO  ==> 
mongo-1          | mongodb 13:24:52.60 INFO  ==> Welcome to the Bitnami mongodb container
mongo-1          | mongodb 13:24:52.61 INFO  ==> ** Starting MongoDB setup **
...
mysql-1          | mysql 13:24:52.61 
mysql-1          | mysql 13:24:52.62 Welcome to the Bitnami mysql container
mysql-1          | mysql 13:24:52.63 INFO  ==> ** Starting MySQL setup **
...

You don't need to manually install or setup those databases anymore. They're all in good hands with docker compose.

If you send some requests to your api server on port 8000, you should see the responses as good as before.

If you want to skip the API server’s docker image building step, you may tune the compose/docker-compose.yml:

@@ -1,6 +1,8 @@
 services:
   lr-rest-books:
-    image: lrbooks:latest
+    build:
+      context: ../
+      dockerfile: Dockerfile
     ports:
       - 8080:8080
     volumes:

Run again:

docker compose up

The compose plugin will build the image for you if needed.

[+] Building 2.8s (18/18) FINISHED                                                                                                                                                                                     docker:desktop-linux
 => [lr-rest-books internal] load build definition from Dockerfile                                                                                                                                                                     0.0s
 => => transferring dockerfile: 1.09kB                                                                                                                                                                                                 0.0s
 => [lr-rest-books internal] load metadata for docker.io/library/alpine:3.19                                                                                                                                                           2.5s
 => [lr-rest-books internal] load metadata for docker.io/library/rust:1.76-alpine3.19                                                                                                                                                  2.5s
 => [lr-rest-books auth] library/alpine:pull token for registry-1.docker.io                                                                                                                                                            0.0s
 => [lr-rest-books auth] library/rust:pull token for registry-1.docker.io                                                                                                                                                              0.0s
 => [lr-rest-books internal] load .dockerignore                                                                                                                                                                                        0.0s
 => => transferring context: 2B                                                                                                                                                                                                        0.0s
 => [lr-rest-books builder 1/6] FROM docker.io/library/rust:1.76-alpine3.19@sha256:e594a9705c4514c0e0b5ed2409f7ec34f20af09a33d242524281247b74196c43                                                                                    0.0s
 => [lr-rest-books stage-1 1/4] FROM docker.io/library/alpine:3.19@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b                                                                                             0.0s
 => [lr-rest-books internal] load build context                                                                                                                                                                                        0.0s
 => => transferring context: 2.15kB                                                                                                                                                                                                    0.0s
 => CACHED [lr-rest-books stage-1 2/4] RUN apk update &&     apk add --no-cache libgcc                                                                                                                                                 0.0s
 => CACHED [lr-rest-books stage-1 3/4] WORKDIR /app                                                                                                                                                                                    0.0s
 => CACHED [lr-rest-books builder 2/6] WORKDIR /app                                                                                                                                                                                    0.0s
 => CACHED [lr-rest-books builder 3/6] RUN apk update &&     apk add --no-cache musl-dev pkgconfig openssl-dev                                                                                                                         0.0s
 => CACHED [lr-rest-books builder 4/6] COPY Cargo.toml .                                                                                                                                                                               0.0s
 => CACHED [lr-rest-books builder 5/6] COPY src/ /app/src                                                                                                                                                                              0.0s
 => CACHED [lr-rest-books builder 6/6] RUN RUSTFLAGS="-Ctarget-feature=-crt-static" cargo build --release --bin lrbooks                                                                                                                0.0s
 => CACHED [lr-rest-books stage-1 4/4] COPY --from=builder /app/target/release/lrbooks .                                                                                                                                               0.0s
 => [lr-rest-books] exporting to image                                                                                                                                                                                                 0.0s
 => => exporting layers                                                                                                                                                                                                                0.0s
 => => writing image sha256:945642d6d2ff652f3e5c53ebc8ddf457f3ed1f7e8b80d6db73616667f08c76a6                                                                                                                                           0.0s
 => => naming to docker.io/library/compose-lr-rest-books                                                                                                                                                                               0.0s
[+] Running 4/0
 ✔ Container compose-redis-1          Created                                                                                                                                                                                          0.0s 
 ✔ Container compose-mongo-1          Created                                                                                                                                                                                          0.0s 
 ✔ Container compose-mysql-1          Created                                                                                                                                                                                          0.0s 
 ✔ Container compose-lr-rest-books-1  Created                                                                                                                                                                                          0.1s 
Attaching to lr-rest-books-1, mongo-1, mysql-1, redis-1
...

Now, you may try all those endpoints with curl. Everything should work even more smoothly than you expected.

Kubernetes

If you want to push your cloud-native solution even further, please try Kubernetes.

It's also known as K8s and is an open-source system for automating deployment, scaling, and management of containerized applications.

You can make a deployment yaml like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: lr-books-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: lr-books
  template:
    metadata:
      labels:
        app: lr-books
    spec:
      containers:
      - name: lr-books-api
        image: lrbooks:latest
        ports:
        - containerPort: 8000

And apply it with this command:

kubectl apply -f lr-books-deployment.yaml