Dockerコンテナで “ちょっとした処理” を “単発のスクリプト実行” をしたい場面があり、簡単なAPIを開発しました。 OneOffDockerRunner は、FastAPI + Docker SDK(Docker-Py)を使って開発された、一度きりのDockerコンテナ実行をサクッとREST APIから行えるソフトウェアです。
たとえば「イメージを指定してコマンド1回だけ実行したい」「イメージを都度pullして認証が必要なプライベートリポジトリにも対応したい」などのニーズに応えます。 今回は、このツールの機能・使い方をざっくり紹介します。
特徴
一度きりのコンテナ実行
- 任意のDockerイメージ・コマンドを指定して、コンテナを1回だけ起動・実行し、終了後は削除します。
docker run --rm ...
に相当するようなイメージです。
コンテナイメージを自動的にpullしてくれる
- APIリクエスト内で指定したDockerイメージは、デフォルトで
pull_policy
が「always」に設定されています。 - そのため、既にローカルにあるイメージでも最新化したい場合や、プライベートレジストリの認証情報を付与してpullしたい場合などにも対応可能。
- 必要に応じて
"pull_policy": "never"
を設定することで、ローカルにある既存のイメージのみを使うこともできます。
- APIリクエスト内で指定したDockerイメージは、デフォルトで
プライベートレジストリへの対応
- Docker Hub、Google Container Registry (GCR)、GitHub Container Registry (GHCR) など、Dockerイメージのプライベートレジストリ認証に対応しています。
環境変数やエントリポイントの指定
env_vars
を渡すことで、実行時に任意の環境変数を簡単に注入できます。entrypoint
を指定すれば、デフォルトのエントリポイントを上書きできます。
出力(stdout・stderr)をAPIレスポンスで取得
- コンテナ実行後の標準出力・標準エラーをまとめてJSONレスポンスで返してくれます。
Base64エンコード済みファイルやディレクトリをコンテナ内に配置可能
- REST APIのリクエストボディにBase64で圧縮したディレクトリやファイルを埋め込み、コンテナ起動前にコンテナ内に展開できます。
- 実行後にファイルやディレクトリをBase64で回収することも可能です。
Docker Volume作成API
- ターゲットディレクトリを
.tar.gz
圧縮 & Base64エンコードして送ると、新規ボリュームにファイル群を展開できます。
- ターゲットディレクトリを
使用方法の概要
1. Dockerコンテナとして実行する場合
docker run --rm \
-p 8222:8000 \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/tumf/oneoff-docker-runner
このコマンドでホストのポート8222とコンテナのポート8000をバインドし、さらにDockerデーモンのソケットをボリュームマウントします。 こうすることでAPIを通じてコンテナを起動・削除できるわけです。
2. コンテナ実行API(POST /run)を叩く
一例として、 curl
で /run
エンドポイントにリクエストを送ってみます:
curl -X 'POST' \
'http://0.0.0.0:8222/run' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"image": "alpine:latest",
"command": [
"/test.sh"
],
"volumes": {
"/app/data": {
"content": "H4sIAGq4eGYAA0tJLEnUZ6AtMDAwMDc1VQDTZhDaw...",
"response": true,
"type": "directory"
},
"/test.sh:ro": {
"mode" : "0755",
"content": "IyEvYmluL2FzaAoKZWNobyAiSGVsbG8sIFdvcmxkISIgPiAvYXBwL2RhdGEvdGVzdC50ZXh0",
"type": "file"
}
}
}'
image
: 実行したいDockerイメージcommand
: 実行コマンド (["/test.sh"]
など配列 or 文字列で指定可能)volumes
: コンテナ内/test.sh
や/app/data
ディレクトリにファイルを差し込む例 (volumeの使い方は少し複雑なのでこのあとの章で説明します)重要なポイント:
OneOffDockerRunnerではデフォルトで「pull_policy」が「always」となっており、コマンド実行時にコンテナレジストリから自動的に最新のイメージをpullします。
もし既にローカルにあるイメージのみを使いたい場合は、以下のように
"pull_policy": "never"
を指定してください。
{
"image": "alpine:latest",
"command": ["echo", "Hello, World!"],
"pull_policy": "never"
}
3. プライベートレジストリにアクセスしたいとき
{
"image": "ghcr.io/your-username/your-private-image:latest",
"auth_config": {
"username": "your-github-username",
"password": "your-personal-access-token",
"serveraddress": "https://ghcr.io"
}
}
このように auth_config
を渡すことで、イメージをpull時にレジストリ認証を行えます。Docker HubやGCR、GHCRなど、各種レジストリで利用可能です。
volumes の使い方詳説
1. /run
の volumes 引数
OneOffDockerRunner の /run
エンドポイントでは、下記のような JSON で volumes
を指定できます。
{
"image": "alpine:latest",
"command": ["/test.sh"],
"volumes": {
"/test.sh:ro": {
"type": "file",
"content": "<base64エンコードしたファイル内容>",
"mode": "0755"
},
"/app/data": {
"type": "directory",
"content": "<base64エンコードした.tar.gz>",
"response": true
}
}
}
volumes のキー
- キー部分:
"コンテナ内のパス(:マウントモード)"
の形式:ro
(read-only)や:rw
(read-write)などのモードを指定できます(未指定の場合はrw
がデフォルト)。- 例:
"/test.sh:ro"
,"/app/data"
など
volumes の値 (VolumeConfig)
- type
"file"
: 1つのファイルとして扱う"directory"
: ディレクトリとして扱う(tar.gzを解凍して配置)"volume"
: 既存 or 新規の “Named Volume” をマウント(※詳細は後述)
- content
- type が
"file"
の場合は、ファイルそのものを base64 エンコードした文字列 - type が
"directory"
の場合は、.tar.gz
に圧縮したディレクトリを base64 エンコードした文字列
- type が
- mode (オプション)
- ファイルに設定するパーミッション(8進数文字列)
"0755"
,"0644"
など。ディレクトリの場合は基本的に無視されます。
- response (オプション)
true
にすると、コンテナ終了後にそのファイル/ディレクトリを再度.tar.gz
化 & base64 エンコードして、APIレスポンスに含めて返却- コンテナの中で変更・生成されたファイルを取得したい場合に便利です。
- name (オプション, type=“volume” のとき)
- “Named Volume” を使いたいときの名前。既に存在する named volume も、その名前があれば使われます。
2. ファイルのマウント例
まずはファイルを1つだけコンテナに配置したいときの例です。
/run
リクエスト:
curl -X POST http://localhost:8222/run \
-H "Content-Type: application/json" \
-d '{
"image": "alpine:latest",
"command": ["/app/hello.sh"],
"volumes": {
"/app/hello.sh:ro": {
"type": "file",
"content": "IyEvYmluL2FzaAoKZWNobyAiSGVsbG8gZnJvbSBmaWxlISIK",
"mode": "0755"
}
}
}'
/app/hello.sh
というファイルを、base64 エンコードしたシェルスクリプトとしてコンテナ内に配置。- モード
0755
を設定して実行可能にしているため、command
に"/app/hello.sh"
を指定し、そのファイルを直接実行。
実行が完了すると、APIレスポンスには実行結果の stdout
/ stderr
が返却されます(例: {"stdout":"Hello from file!\n","stderr":""...}
)。
3. ディレクトリのマウント例
続いて、ディレクトリを展開したい場合です。 .tar.gz
にまとめたディレクトリを base64 化して content
に指定します。
/run
リクエスト:
curl -X POST http://localhost:8222/run \
-H "Content-Type: application/json" \
-d '{
"image": "alpine:latest",
"command": ["ls", "-l", "/app"],
"volumes": {
"/app": {
"type": "directory",
"content": "H4sIAAAAAAAAA2NgYGBg....<省略>....AAAA",
"response": true
}
}
}'
"type": "directory"
を指定すると、.tar.gz
を一旦/tmp
に展開して、そのディレクトリ一式をコンテナにマウントします。response: true
に設定すると、コンテナ終了後に/app
下の内容を.tar.gz
+ base64 でレスポンスとして返却。 コンテナ内で生成したデータを簡単にAPI経由でダウンロード可能です。
4. Named Volume (type=“volume”)
4.1. /run
で named volume を利用
{
"image": "alpine:latest",
"command": ["echo", "test volume"],
"volumes": {
"/data": {
"type": "volume",
"name": "my-shared-volume"
}
}
}
type="volume"
&name="my-shared-volume"
を指定すると、DockerのNamed Volume「my-shared-volume」を/data
にマウントします。- 既に存在するNamed Volumeなら再利用、新規の場合はDockerが自動で作成します。
response: true
は サポートしていません(内部実装的には無視されます)。Named Volume はコンテナ終了後もホストにデータが残り続けるため、一時的なファイル抽出とは少し性質が異なるからです。
4.2. /volume
エンドポイントで named volume を作成
OneOffDockerRunner には /volume
というエンドポイントもあります。
これにより、先に named volume を作成し、中にファイル一式を展開しておくことが可能です。
curl -X POST http://localhost:8222/volume \
-H "Content-Type: application/json" \
-d '{
"name": "my-volume",
"content": "H4sIAIQOfmYAA+2TMQ7DIAxF...<base64圧縮ディレクトリ>...A==",
"driver": "local",
"driver_opts": {},
"labels": {"created_by": "my-service"}
}'
これで「my-volume」という Named Volume を作成し、base64 で圧縮されたファイル/ディレクトリを展開しておきます。
- content:
tar.gz
ディレクトリをbase64エンコードした文字列 - driver (オプション): デフォルトは
local
- driver_opts / labels (オプション): volume 作成時に付与するオプションやラベル
4.3. 作成した named volume を /run
でマウント
上で作成した named volume を /run
でもう一度使用する場合は、次のように指定します:
{
"image": "alpine:latest",
"volumes": {
"/mydata": {
"type": "volume",
"name": "my-volume"
}
},
"command": ["ls", "/mydata"]
}
こうすることで、事前に /volume
エンドポイントで仕込んだファイルが /mydata
に含まれた状態でコンテナが起動します。
ソースコード構成・GitHub Actions
このリポジトリでは、以下のディレクトリ/ファイル構成になっています:
oneoff-docker-runner
├── .github
│ └── workflows
│ ├── docker-publish.yml
│ └── python-app.yml
├── Dockerfile
├── main.py
├── healthcheck.py
├── requirements.txt
├── tests
│ └── test_run_container.py
└── ...
docker-publish.yml
プッシュやタグをトリガーにコンテナイメージをビルドし、GitHub Container Registry (GHCR) へ自動でPushしています。- マルチプラットフォーム(
linux/amd64
,linux/arm64
)向けのイメージビルド - Buildxを使いキャッシュを効率的に利用
- cosignを使ったイメージ署名にも対応
- マルチプラットフォーム(
python-app.yml
PythonのユニットテストおよびLint(flake8, pytest)をGitHub Actionsで自動実行し、プルリクエストごとに品質を担保します。Python 3.9 / 3.10 / 3.11のマルチバージョンでテストしています。main.py
FastAPIアプリケーションの本体。/run
や/volume
、/health
といったエンドポイントが定義されており、Dockerコンテナの実行やVolume作成ロジックが記述されています。pull_policy
パラメータで「always/never」を選択するだけで、自動pullのON/OFFを切り替えられるよう設計されています。
Dockerfile
FastAPI (uvicorn) を起動するためのイメージ定義。requirements.txt
をインストールした上で、CMD ["uvicorn", "main:app", ...]
でアプリを起動します。test_run_container.py
fastapi.testclient
とunittest.mock
を使って、コンテナ実行が正しく完了するかをテストします。テスト内では実際のdockerコマンド呼び出しをMock化し、stdout・stderr のテストができるようにしています。
OneOffDockerRunner は、
- 「とりあえず1回だけコンテナを動かしたい」
- 「コンテナイメージをpullしながら使いたい(または、すでに手元にあるイメージだけ使いたい)」
- 「プライベートレジストリからPullして何か一瞬動かしたい」
- 「API経由で簡単にDockerタスクを実行・管理したい」
といったユースケースにピッタリのシンプルなREST APIサービスです。 ぜひ試してみたい方はGitHubリポジトリをクローンし、ローカルやDockerホストで起動してみてください。Pullリクエスト・Issueも大歓迎です!
- GitHubリポジトリ: tumf/oneoff-docker-runner