はじめに
Docker開発が苦手!俺は業務ロジックを作りたい!しかしこの手の開発手法は当面なくなりそうにないので、とにかく仕事に近い感じのアレンジをしながら作ってみる。アレルギーを克服しろ!記事の量は最低限に!
最低限のファイル構成
こんな感じ(モノRepositoryはベストプラクティスではない。現場がそうだってだけ)
docker_world
├── .gitignore
├── frontend
│ ├── .devcontainer
│ │ ├── devcontainer.json
│ │ └── Dockerfile
│ ├── src
│ │ └── pages
│ │ └── index.tsx
│ ├── package.json
│ └── README.md
└── backend
├── .devcontainer
│ ├── devcontainer.json
│ ├── Dockerfile
│ └── setup.sh
├── src
│ ├── tests
│ │ └── test_main.py
│ └── main.py
├── .gitignore
├── docker-compose.yml
├── README.md
└── requirements.txt
gitの設定
改行コードのLF統一
Git による改行コード管理が自動化され(ローカルでコミットされる際に CRLF を LF に自動変換します。)、リポジトリ内の改行コードをすべて LF で統一できるようになる。仕事でも環境面で大ハマリする筆頭だと思う
git config --global core.autocrlf input
whoami
whoami
※Dockerfileを試行錯誤するときは sleep infinity を使え
こうすると $PATH に追加すべきパスがなにかわかんねーってときにコンテナの世界にい続けたまま which uvicorn
などで調査できる
:
# uvicorn を起動コマンドとして設定
# CMD ["python", "uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["sleep", "infinity"]
※Dockerfileにおける WORKDIR を /workspace に統一する理由
Dockerfile
において WORKDIR
を /home/vscode
など一般ユーザの「ホームディレクトリ」に設定していた時期があった。
問題: ホームディレクトリには 意外に多くの隠しファイル が生成される。
.cache/
.vscode-server/
.ssh/
.gitconfig
.bash_history
これらのファイルは環境依存の設定や一時的なデータであり、プロジェクトのコード管理には不要である。これらのファイルを .gitignore
に追加するのは思考の妨げになる。
解決策:
-
/workspace
をWORKDIR
に指定する 公式: WORKDIR- コンテナはホストOSとは独立した環境であるため、Linuxシステムに従ったディレクトリ配置をしなくてもよい
-
/workspace
配下にはプロジェクトに必要なファイルのみが存在するため、コンテナ内の環境をシンプルに保つことができる
ホームディレクトリを直接 WORKDIR に設定するのは避けたほうが良い。
(※要見直し)
frontend
File - Open Folder
で frontend のフォルダを開く
devcontainer.json(新規)
{
"name": "Node.js Dev Container",
"dockerFile": "./Dockerfile",
"postCreateCommand": "npm i"
}
Dockerfile(新規)
ENVとRUNのapt-getのとこはpythonやnextjsでもかまわずコピペでやってる
FROM node:latest
ENV TZ=Asia/Tokyo
RUN apt-get update && apt-get install -y \
bash curl git openssh-client zip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
package.json(新規)
このセットはたしかNext.jsをすっぴんセットアップしてできあがるやつだったような気がする。ご自由にどうぞ
{
"name": "next.js",
"version": "1.0.0",
"description": "Your project description",
"main": "index.js",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "13.2.4",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@types/node": "22.7.4",
"@types/react": "18.3.11",
"typescript": "5.6.2"
}
}
devcontainerを起動
左下のへんなマークを押して Reopen in Conteiner
devcontainerを起動したときの処理の流れは
- devcontainer が dockerFile をビルドする
- 終わったら postCreateCommand を実行する
{
"name": "Node.js Dev Container",
"dockerFile": "./Dockerfile",
"postCreateCommand": "npm i"
}
ハマりがちなのが、親ディレクトリに遡ることはできませんという仕様。pip install
を Dockerfile
のなかでやろうとしたときに、大概この仕様で大ハマリする
(ChatGPT)
Dockerfile内では、ビルドコンテキストが制限されており、親ディレクトリに遡ることはできません。これはセキュリティとコンテナの隔離を保つためです。具体的には、以下のような制限があります:
ビルドコンテキストの制限: Dockerfileは、指定されたビルドコンテキスト内でのみファイルをコピーしたり、アクセスしたりできます。ビルドコマンドを実行する際に指定したディレクトリ(例えば、docker build -t myapp .で.を指定した場合、そのディレクトリがビルドコンテキストになります)に制限されます。
COPYコマンドの制約: COPYやADDコマンドで親ディレクトリやそれ以外のディレクトリからファイルをコピーすることはできません。例えば、COPY ../file.txt /app/のように親ディレクトリを指定すると、エラーが発生します。
index.tsx
import type { NextPage } from 'next'
const Home: NextPage = () => {
return (
<div>
<h1>Hello World</h1>
</div>
)
}
export default Home
README.md
## サーバ起動
# npm run dev
サーバ起動
# npm run dev
backend
.devcontainer/devcontainer.json
{
"name": "FastAPI Dev Container",
"dockerComposeFile": "../docker-compose.yml",
"service": "fastapi",
"postCreateCommand": "/workspace/.devcontainer/setup.sh",
"workspaceFolder": "/workspace",
"containerEnv": {
"SHELL": "/bin/bash"
},
"customizations": {
"vscode": {
"settings": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave":true,
"editor.formatOnPaste": true
},
"extensions": [
"ms-python.black-formatter",
"ms-python.flake8",
"humao.rest-client"
]
}
}
}
docker-compose.yml
services:
fastapi:
build:
context: .
dockerfile: .devcontainer/Dockerfile
volumes:
- .:/workspace
ports:
- "8000:8000"
networks:
default:
name: dev_network
ハマりがちなのが、親ディレクトリに遡ることはできませんという仕様
Dockerfile
内では、親ディレクトリに遡ることはできません。これはセキュリティとコンテナの隔離を保つためです。具体的には、以下のような制限があります:
ビルドコンテキストの制限:
Dockerfile
は、指定されたビルドコンテキスト内でのみファイルをコピーしたり、アクセスしたりできます。ビルドコマンドを実行する際に指定したディレクトリ(例えば、docker build -t myapp .
で .
を指定した場合、そのディレクトリがビルドコンテキストになります)に制限されます。
COPYコマンドの制約
COPY
や ADD
コマンドで親ディレクトリやそれ以外のディレクトリからファイルをコピーすることはできません。例えば、COPY ../file.txt
/app/
のように親ディレクトリを指定すると、エラーが発生します。
.devcontainer/Dockerfile
FROM python:3.12.9-slim-bullseye
# 共通変数
ARG WORKDIR=/workspace
ARG USER_NAME=vscode
ARG USER_UID=1000
ARG USER_GID=$USER_UID
ENV TZ=Asia/Tokyo
# ユーザー作成
RUN groupadd --gid $USER_GID $USER_NAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USER_NAME
# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
bash curl git openssh-client zip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# 作業ディレクトリを設定
WORKDIR $WORKDIR
# ユーザー変更
USER $USER_NAME
# 確認: ここでsleepしないとコンテナが終わってしまう
#CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["sleep", "infinity"]
.devcontainer/setup.sh
#!/bin/bash
# 共通変数
WORKDIR="/workspace"
VENV_DIR="$WORKDIR/.venv"
# 仮想環境の作成
if [ ! -d "$VENV_DIR" ]; then
echo "仮想環境を作成中..."
python -m venv $VENV_DIR
else
echo "仮想環境は既に存在します。"
fi
# 仮想環境をアクティベート
source $VENV_DIR/bin/activate
# 仮想環境の確認
echo "現在のPython環境: $(which python)"
# 仮想環境が有効か確認
if [ "$VIRTUAL_ENV" != "" ]; then
echo "仮想環境が有効です(\$VIRTUAL_ENV): $VIRTUAL_ENV"
else
echo "仮想環境が有効ではありません。"
exit 1
fi
# requirements.txt の内容をインストール
if [ -f "$WORKDIR/requirements.txt" ]; then
echo "requirements.txt からパッケージをインストール中..."
pip install --upgrade pip
pip install --no-cache-dir -r $WORKDIR/requirements.txt
else
echo "requirements.txt が見つかりませんでした。"
fi
echo "仮想環境のセットアップが完了しました。"
requirements.txt
fastapi==0.115.8
uvicorn==0.34.0
pytest==8.3.4
.gitignore
__pycache__/
*.log
*.swp
.env
.venv/
src/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}
def reverse(text: str) -> str:
"""
与えられた文字列を逆順にして返す。
この関数はpytestのテスト用に作成されています。
Args:
text (str): 逆順にする文字列。
Returns:
str: 逆順になった文字列。
"""
return text[::-1]
src/tests/test_main.py
from src.main import reverse
def test_reverse():
assert reverse("hello") == "olleh"
assert reverse("") == ""
assert reverse("123") == "321"
assert reverse("あいう") == "ういあ"
.vscode/launch.json
F5
でサーバー起動とpytestが打てるようになる
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Serve",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": [
"src.main:app",
"--reload",
"--port", "8000",
"--host", "0.0.0.0",
],
},
{
"name": "Run Tests",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["-vv"]
}
]
}