🐳
【DevOps実践】RustでAPIサーバーを構築してみた②: Docker構築編
前置き
【DevOps実践】RustでAPIサーバーを構築してみた①: 全体紹介編で全体像を紹介してます。
なにやってるかが分からない方は読んでみてください。
Docker構築
アプリケーション開発で使用するDockerを構築していきます。
マルチステージビルドでbase, dev, builder, runtimeに分けていきます。
以下の内容は全部同じDockerfileで記述します。
Base: 全ステージ共通項目
# 使用するRustのバージョン
ARG RUST_VERSION=1.88.0
FROM rust:${RUST_VERSION}-slim-bookworm AS base
# Rustのコンパイルで使用するパッケージをインストール
RUN apt-get update && apt-get install -y \
pkg-config libssl-dev build-essential \
&& rm -rf /var/lib/apt/lists/*
Dev: 開発環境
ユーザー作成、ワークディレクトリ、必要なパッケージは自由にやってください。
自分はDevContainerにする予定なので、ホストPC側と権限合わせたり、ユーザー作成してます。
FROM base AS dev
# ユーザー情報(必要に応じて調整)
ARG USER_NAME=rust
ARG USER_UID=1000
ARG USER_GID=1000
# 開発時に必要なパッケージをインストール
RUN apt-get update && apt-get install -y sudo curl && rm -rf /var/lib/apt/lists/*
# ユーザー作成、sudoをパスワードなしで実行できる設定
RUN groupadd --gid ${USER_GID} ${USER_NAME} \
&& useradd --uid ${USER_UID} --gid ${USER_GID} -m ${USER_NAME} \
&& echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
RUN rustup component add rustfmt clippy
USER ${USER_NAME}
WORKDIR /home/${USER_NAME}/app
# SQLx CLIのインストール、UVインストール
RUN cargo install sqlx-cli --no-default-features --features postgres \
&& cargo install --git https://github.com/astral-sh/uv uv
Builder: 本番環境イメージビルド用
今回はSQLxを使っているため、ビルド時にDBに接続してSQL実行可能かのチェックが入ります。
そのために毎回Docker作るのが面倒なので、SQLX_OFFLINEで実行します。
SQLX_OFFLINEで実行できるよう、開発環境で以下のコマンドを実行し、型ファイルを生成します。cargo sqlx prepare --workspace -- --bin appというのも、ローカルでDockerImageをビルドする場合の話ですが、今回はGithubActionsで自動化してきます。
FROM base AS builder
# 開発環境みたいにユーザー作成してないので、
# ルートディレクトリ直下でディレクトリを指定
WORKDIR /app
# 依存解決キャッシュ
COPY Cargo.toml Cargo.lock ./
COPY extensions ./extensions
# 依存解決キャッシュ用のダミーファイルを配置
RUN mkdir -p src \
&& echo "pub fn dummy() {}" > src/lib.rs \
&& echo "fn main() { println!(\"dummy\"); }" -> src/main.rs \
&& cargo build --release
# アプリケーション全体をコピー
COPY . .
ENV SQLX_OFFLINE=true
RUN cargo build --release --bin app --features sqlx-release
Runtime: 本番用イメージ
このイメージがCloudRunで実行する本体です。
distrolessがよくわからない方むけにちょっと説明:
Google が提供する Docker コンテナのベースイメージ
不要なコンポーネントを削除した最小限のイメージであり、セキュリティ上のメリットがあります
ArtifactRegistryはリポジトリ容量が0.5GB越えなければ無料で使用できるため、
最小限にビルドできるように努力しましょう。
FROM gcr.io/distroless/cc-debian12 AS runtime
WORKDIR /app
# app続きでちょっと紛らわしいですが、解説すると
# /app/target/release/app:
# 先頭の/appはディレクトリ名
# 最後のappはアプリケーションをビルドした成果物名
# /app/app: builderステージでビルドした成果物をそのまま持ってきた
copy /app/target/release/app /app/app
# CloudRunで実行するには、アプリケーションが8080番でリッスンしている必要がある
ENV PORT=8080
ENV HOST=0.0.0.0
# ルートユーザなしで実行する
USER nonroot:nonroot
EXPOSE 8080
CMD ["./app"]
おまけ
Posrgresql
ARG POSTGRES_VERSION=17
FROM postgres:${POSTGRES_VERSION}
# これはある人だけ
COPY ./docker/postgres/init.sql /docker-entrypoint-initdb.d/
COPY ./docker/postgres/postgresql.conf /etc/postgresql/postgresql.conf
# Entrypointは保持して、CMDで構成ファイルを指定
CMD ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"]
compose.yaml
services:
app:
container_name: actix-web-sqlx
networks:
- actix-web-net
build:
context: .
dockerfile: ./docker/rust/Dockerfile
target: dev
tty: true
ports:
- 8000:8080
depends_on:
- db
- test-db
- redis
volumes:
- .:/home/rust/app
db:
container_name: actix-db
networks:
- actix-web-net
build:
context: .
dockerfile: ./docker/postgres/Dockerfile
restart: unless-stopped
volumes:
- ./docker/postgres/postgresql.conf:/etc/postgresql/postgresql.conf
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
test-db:
container_name: actix-test-db
networks:
- actix-web-net
build:
context: .
dockerfile: ./docker/postgres/Dockerfile
restart: unless-stopped
volumes:
- ./docker/postgres/postgresql.conf:/etc/postgresql/postgresql.conf
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
redis:
container_name: actix-redis
image: redis:latest
networks:
- actix-web-net
networks:
actix-web-net:
volumes:
actix-postgres:
actix-postgres-test:
DevContainer
# compose.yaml
services:
app:
volumes:
- ..:/workspaces:cached
command: sleep infinity
DevContainerは開発効率優先でRustAnalyzerの機能を一部オフにしてます。
# devcontainer.json
{
"name": "actix-web-app",
"dockerComposeFile": [
"../compose.yaml",
"compose.yaml"
],
"service": "app",
"features": {
"ghcr.io/nils-geistmann/devcontainers-features/zsh:0": {},
"ghcr.io/devcontainers/features/git:1": {}
},
"postCreateCommand": "bash -c 'git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-\"$HOME/.oh-my-zsh/custom\"}/plugins/zsh-autosuggestions && git clone https://github.com/zsh-users/zsh-syntax-highlighting ${ZSH_CUSTOM:-\"$HOME/.oh-my-zsh/custom\"}/plugins/zsh-syntax-highlighting && git clone https://github.com/zsh-users/zsh-completions ${ZSH_CUSTOM:-\"$HOME/.oh-my-zsh/custom\"}/plugins/zsh-completions && sed -i \"s/plugins=(git)/plugins=(git zsh-autosuggestions zsh-completions zsh-syntax-highlighting)/\" ~/.zshrc'",
"customizations": {
"vscode": {
"settings": {
"editor.tabSize": 4,
"terminal.integrated.defaultProfile.osx": "zsh",
"workbench.colorTheme": "Monokai Vibrant (Rust)",
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
},
"rust-analyzer.check.command": "clippy",
"rust-analyzer.highlighting.strings": false,
"rust-analyzer.checkOnSave.enable": false,
"rust-analyzer.procMacro.enable": true,
"rust-analyzer.cargo.runBuildScripts": false,
"rust-analyzer.diagnostics.enable": true
},
"extensions": [
"DioxusLabs.monokai-vibrant-rust",
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"streetsidesoftware.code-spell-checker",
"fill-labs.dependi",
"qufiwefefwoyn.inline-sql-syntax"
]
}
}
}
Discussion