はじめに

シェル芸オンラインジャッジというウェブサイトを開発しています。 今回はフロントエンドとバックエンドをDockerコンテナ内で動かすように変更しました。 nginxもコンテナ内で動かしています。

シェル芸オンラインジャッジ

修正方法について

シェル芸オンラインジャッジのソースコードを丸ごとGeminiに渡して、 具体的に修正方法を聞いて修正しました。 今回はDockerfileやdocker-compose.yml、nginxのconfファイルなどについて教えてもらいました。

シェル芸オンラインジャッジのシステム構成図

Nano Banana Proに生成してもらった現状のシステム構成図です。

システム構成

Dockerfile

Frontend

下記がフロントエンドのDockerfileです。 主にフロントエンド側のビルドとnginxの設定・起動を行っています。 問題データのファイルもコンテナ内にコピーしています。

# --- Build Stage ---
FROM node:22 AS builder

WORKDIR /app

COPY frontend/package.json frontend/yarn.lock* ./
RUN yarn install --frozen-lockfile

COPY frontend/ ./
RUN yarn build

# --- Serve Stage ---
FROM nginx:alpine

# ビルド成果物をNginxの公開ディレクトリにコピー
COPY --from=builder /app/build /usr/share/nginx/html

# 問題データを公開ディレクトリにコピー
COPY problems/ /usr/share/nginx/html/

# Nginx設定ファイルをコピー
COPY frontend/nginx/default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

nginxの設定ファイルはあらかじめ用意しておいてコンテナ内へコピーします。 一応80番ポートの設定を書いていますが、実際は443ポートだけ受け付けます。

server {
    listen 80;
    server_name shellgei-online-judge.com localhost;

    # HTTP -> HTTPS リダイレクト
    # location / {
    #     return 301 https://$host$request_uri;
    # }
}

server {
    listen 443 ssl;
    server_name shellgei-online-judge.com localhost;

    # SSL証明書の設定
    ssl_certificate /etc/letsencrypt/live/shellgei-online-judge.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/shellgei-online-judge.com/privkey.pem;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    root /usr/share/nginx/html;
    index index.html;

    # Docker内部のDNSサーバーを指定
    resolver 127.0.0.11 valid=30s;

    # フロントエンド (React)
    location / {
        try_files $uri $uri/ /index.html;
    }

    # バックエンド (FastAPI) へのプロキシ
    location /api {
        proxy_pass http://backend:8000;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Backend

下記がバックエンドのDockerfileです。 主にFastAPIの環境の作成を行っています。 こちらでも問題データのファイルをコンテナ内にコピーしています。

unixtime.txtやshellgei_id.txtというサーバ側のデータ管理用ファイルも作成しています(ここで作成していますが最終的にはdocker-compose.ymlのほうでホストからマウントしています)。 データ管理に関してはいずれデータベースをコンテナ内で動かす方針に移行したいです。

FROM python:3.12-slim

WORKDIR /app

# Poetryのインストール
RUN pip install poetry

# 依存関係ファイルのコピー (キャッシュ効率化のため先にコピー)
COPY pyproject.toml poetry.lock* ./

# 仮想環境を作成せずにシステムにインストール
RUN poetry config virtualenvs.create false \
    && poetry install --no-interaction --no-ansi --no-root

# ソースコードのコピー
COPY backend/ ./backend/

# 問題データのコピー
COPY problems/ ./backend/public/

# 作業ディレクトリをbackendに変更
WORKDIR /app/backend

RUN echo "0.0" > unixtime.txt && chmod 666 unixtime.txt \
    && echo "0" > shellgei_id.txt && chmod 666 shellgei_id.txt

# uvicornサーバーの起動
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose.yml

下記がシステム全体を起動するdocker-compose.ymlです。 バックエンドとフロントエンドのコンテナをそれぞれ起動しています。 バックエンド側ではコンテナ内からコンテナを操作するためにホスト側のDockerソケットをマウントしています。

フロントエンド側ではnginxの設定ファイルをマウントして使用しています。 また、ホスト側で用意したSSL証明書をマウントしています。

version: '3.8'

services:
  backend:
    build:
      context: .
      dockerfile: backend/Dockerfile
    container_name: soj-backend
    restart: always
    volumes:
      # ホストのDockerソケットをマウント (DooD)
      - /var/run/docker.sock:/var/run/docker.sock
      # データファイル
      - ./backend_data/shellgei_id.txt:/app/backend/shellgei_id.txt
      - ./backend_data/unixtime.txt:/app/backend/unixtime.txt
    environment:
      - TZ=Asia/Tokyo

  frontend:
    build:
      context: .
      dockerfile: frontend/Dockerfile
    container_name: soj-frontend
    restart: always
    ports:
      - "80:80"    # HTTP
      - "443:443"  # HTTPS
    volumes:
      - ./frontend/nginx/default.conf:/etc/nginx/conf.d/default.conf
      # ホスト側のSSL証明書のマウント
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - backend

バックエンドのデータ管理ファイル

実行するシェル芸の時刻管理やID付けのために使用するデータファイルを作成するbashファイルを用意しました。 運用としてスマートではないので、今後データベースを使用するなどで対応したいです。

mkdir -p backend_data
echo "0.0" > backend_data/unixtime.txt
echo "0" > backend_data/shellgei_id.txt
chmod 666 backend_data/unixtime.txt
chmod 666 backend_data/shellgei_id.txt

起動方法

シェル芸オンラインジャッジのリポジトリをダウンロードしてから下記コマンドを実行することで起動できます。 -dオプションを付けてバックグランドで実行しています。

cd /path/to/ShellgeiOnlineJudge/
bash create_backend_data.bash
docker compose up -d --build

以前より簡単に起動できるようになりました。 また、nginxの設定をしなくてよくなったことなど、ホスト側での準備が少なく済むようになって良かったです。

おわりに

今回はシェル芸オンラインジャッジのシステムをDockerコンテナ内で動かせるように変更を加えました。 Geminiに聞いたら何でも教えてくれるのでとても助かります(たまに解決できないこともありましたが仕方なしです)。 運用が少し楽になって良かったです。 この調子でどんどん改善できたらと思います。 それでは、また。