はじめに
TROCCOのSelf-Hosted Runnerは、TROCCOのデータ転送をユーザー管理の処理環境で実行できる機能です。SaaSとしてのTROCCOの使いやすさを保ちつつ、実データを閉域内に留めることでセキュアなデータ転送を実現することができます。
Docker(Linux環境)があれば利用できるので、クラウドのContainer as a Serviceを使うことも多くあるでしょうが、要件によってはサーバ、VMでの利用も想定されます。今回は、シンプルな構成としてdocker-composeの構成例をご紹介します。
こんな方におすすめ
- docker-composeを利用したTROCCO Self-Hosted Runnerの運用を検討したい方
※Windows PCのDocker Desktop(WSL2)が利用可能な環境で検証していますが、Linux環境のDockerが使える環境であれば同じように利用できると思われます
※Docker Engineおよびdocker-composeが利用可能であることが前提です
本記事で紹介する構成のサンプルコードはGitHubで公開しています。実際に動かしてみたい場合はご利用ください。
docker-composeとは
docker-composeとは、複数のコンテナで構成されるアプリケーションを YAML ファイル(docker-compose.yaml) に定義し、まとめて起動・停止・管理できるツールです。
Self-Hosted Runnerはdocker runで個別のコンテナを立てることでも利用できますが、docker-composeを利用することでコンテナの停止時に自動で再起動したり、リソースの違うRunnerをまとめて立てたり、Runnerのログをファイルに書き出ししたりなどすることができます。
docker-compose.yamlファイルが存在するディレクトリをカレントディレクトリとして、
docker-compose up -dでコンテナを起動docker-compose down -vでコンテナを停止
できます。

docker-composeの構成例
それほど量は多くないので、まずコードをまとめて掲載します。ディレクトリ構成は以下の通りです。
.
├── .env.example
├── .gitignore
├── daily_rotate.lua
├── docker-compose.yaml
├── fluent-bit.conf
├── fluent-bit-json-parser.conf
├── Makefile
└── README.md
.env.example
docker-compose.yaml で参照する環境変数のテンプレートです。利用時はこのファイルを .env にコピーし、各値を実値に置き換えてください。Runnerを2台構成にしているため、Registration TokenはRunnerごとに別々の値を指定できます(同じ値でも問題ありません)。
TROCCO_IMAGE_URL=<TROCCO_SELF_HOSTED_RUNNER_IMAGE_URL>
TROCCO_REGISTRATION_TOKEN_NORMAL=<YOUR_REGISTRATION_TOKEN_NORMAL>
TROCCO_REGISTRATION_TOKEN_ENHANCED=<YOUR_REGISTRATION_TOKEN_ENHANCED>
docker-compose.yaml
インバウンド通信を受け付けない、メモリが異なるRunnerを2台と、Runnerのログをファイルに書き出すためのfluent-bitのコンテナ、古いログファイルを自動削除するlog-cleanerのコンテナを設定します。
services:
trocco-self-hosted-runner-normal:
image: ${TROCCO_IMAGE_URL}:latest
mem_limit: 2G
cpus: 2
restart: always
environment:
- TROCCO_REGISTRATION_TOKEN=${TROCCO_REGISTRATION_TOKEN_NORMAL}
- TROCCO_PREVIEW_SEND=true
logging:
driver: "fluentd"
options:
fluentd-address: "127.0.0.1:24224"
tag: "runner.log"
fluentd-async: "true"
labels: "com.docker.compose.service"
networks:
- egress
depends_on:
- fluent-bit
trocco-self-hosted-runner-enhanced:
image: ${TROCCO_IMAGE_URL}:latest
mem_limit: 4G
cpus: 2
restart: always
environment:
- TROCCO_REGISTRATION_TOKEN=${TROCCO_REGISTRATION_TOKEN_ENHANCED}
- TROCCO_PREVIEW_SEND=true
logging:
driver: "fluentd"
options:
fluentd-address: "127.0.0.1:24224"
tag: "runner.log"
fluentd-async: "true"
labels: "com.docker.compose.service"
networks:
- egress
depends_on:
- fluent-bit
fluent-bit:
image: fluent/fluent-bit:latest
volumes:
- ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
- ./fluent-bit-json-parser.conf:/fluent-bit/etc/fluent-bit-json-parser.conf
- ./daily_rotate.lua:/fluent-bit/etc/daily_rotate.lua
- ./log:/fluent-bit/log
restart: always
ports:
- "127.0.0.1:24224:24224"
logging:
driver: "local"
options:
max-size: "50m"
max-file: "10"
networks:
- log_ingest
log-cleaner:
image: alpine:latest
volumes:
- ./log:/log
entrypoint: ["/bin/sh", "-c"]
command: ["while true; do find /log -name '*.jsonl' -mtime +30 -delete; sleep 86400; done"]
restart: always
network_mode: none
logging:
driver: "local"
options:
max-size: "1m"
max-file: "3"
networks:
egress:
driver: bridge
log_ingest:
driver: bridge
fluent-bit.conf
fluent-bitのパイプラインを記載します。Dockerの fluentd ログドライバから受け取ったログに対し、JSON のフラット化・日付別のファイル分割・メタ情報のネストなどを行い、Runnerごとに別ディレクトリへ書き出します。
# Dockerのfluentdログドライバーからforwardプロトコルでログを受信
[INPUT]
Name forward
Listen 0.0.0.0
Port 24224
# カスタムパーサー定義の読み込み
[SERVICE]
Parsers_File /fluent-bit/etc/fluent-bit-json-parser.conf
# 空文字のログ(stderrの空行)を除外
[FILTER]
Name grep
Match runner.log
Exclude log ^$
# logフィールド内のJSON文字列をパースしてフラットなJSONLにする
[FILTER]
Name parser
Match runner.log
Key_Name log
Parser docker_json
Reserve_Data On
# com.docker.compose.service ラベルから新しいタグ文字列 new_tag (= <service>/<date>.jsonl) を組み立てる
[FILTER]
Name lua
Match runner.log
script /fluent-bit/etc/daily_rotate.lua
call daily_rotate
# new_tag フィールドの値をそのまま新しいタグにする (タグ内の '/' は出力時にサブディレクトリになる)
[FILTER]
Name rewrite_tag
Match runner.log
Rule $new_tag ^(.+)$ $1 false
Emitter_Name date_rewriter
# タグ書き換え用の一時フィールドと、出力ファイルに残したくないラベル参照を除去
[FILTER]
Name modify
Match */*.jsonl
Remove new_tag
Remove com.docker.compose.service
# container_id, container_name, sourceをmetaにまとめる
[FILTER]
Name nest
Match */*.jsonl
Operation nest
Wildcard container_id
Wildcard container_name
Wildcard source
Nest_under meta
# Path = /fluent-bit/log にタグをそのままパスとして連結
# 例: タグが trocco-self-hosted-runner-normal/20260424.jsonl なら /fluent-bit/log/trocco-self-hosted-runner-normal/20260424.jsonl に出力
[OUTPUT]
Name file
Match */*.jsonl
Path /fluent-bit/log
Mkdir On
Format plain
fluent-bit-json-parser.conf
fluent-bit.conf の [FILTER] parser から参照されるパーサー定義です。Runnerが標準出力に書き出すJSONログを構造化データとして読み取るために利用します。
[PARSER]
Name docker_json
Format json
daily_rotate.lua
fluent-bit.conf の [FILTER] lua から呼ばれるLuaスクリプトです。com.docker.compose.service ラベル(= Docker Composeのservice名)とレコードのタイムスタンプから <service>/YYYYMMDD.jsonl 形式の完成済みタグ文字列を生成し、new_tag フィールドとして付加します。後続の rewrite_tag がこの値を新しいタグに採用することで、ログがservice別・日付別のファイルに書き出されます。
function daily_rotate(tag, timestamp, record)
-- docker-composeのservices名は com.docker.compose.service ラベル経由でレコードに乗ってくる
local service_name = record["com.docker.compose.service"] or ""
local date_str = os.date("!%Y%m%d", timestamp)
-- fluent-bit 側の rewrite_tag では $1 だけで使うので、ここで完成形のタグを作る
record["new_tag"] = service_name .. "/" .. date_str .. ".jsonl"
return 1, timestamp, record
end
ポイントの解説
上記のコードに関して、ポイントは以下の通りです。
Runnerの設定
以下のシンプルなコードでRunnerの設定を定義できます。メモリやCPUは実行するワークロードに応じて調整してください。
また、転送の内容によっては強めのリソースが必要になるかもしれませんが、その場合はSelf-Hosted Runner Clusterを分けて、それに対応したRegistration TokenをRunnerに登録することで、転送設定ごとのRunnerの使い分けができます。
trocco-self-hosted-runner-normal:
image: ${TROCCO_IMAGE_URL}:latest
mem_limit: 2G
cpus: 2
restart: always
environment:
- TROCCO_REGISTRATION_TOKEN=${TROCCO_REGISTRATION_TOKEN_NORMAL}
- TROCCO_PREVIEW_SEND=true
logging:
driver: "fluentd"
options:
fluentd-address: "127.0.0.1:24224"
tag: "runner.log"
fluentd-async: "true"
labels: "com.docker.compose.service"
networks:
- egress
depends_on:
- fluent-bit
tag はfluent-bit側のパイプラインを起動するための識別子で、複数Runner間で共通の runner.log を指定しています。labels: "com.docker.compose.service" を指定することで、Docker Composeが自動で各コンテナに付与する com.docker.compose.service ラベル (= サービス名) がログレコードのフィールドとして送出され、fluent-bit側で どのRunnerからのログかを判別する のに使えます。
ログの連携
コンテナの稼働の確認や、何かエラーが起きたときのデバッグをするために、ログを適切に取り扱うことは重要です。今回はコンテナでfluent-bitを動かし、Runnerのログをそちらに向けて、ファイルとしてエクスポートするようにしています。
fluent-bit:
image: fluent/fluent-bit:latest
volumes:
- ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
- ./fluent-bit-json-parser.conf:/fluent-bit/etc/fluent-bit-json-parser.conf
- ./daily_rotate.lua:/fluent-bit/etc/daily_rotate.lua
- ./log:/fluent-bit/log
restart: always
ports:
- "127.0.0.1:24224:24224"
logging:
driver: "local"
options:
max-size: "50m"
max-file: "10"
networks:
- log_ingest
今の設定のままで、以下のような形でログがローカルで取扱えるようになります。1行1イベントのJSON Linesで、Runnerが出力したフィールド(msg / level / time など)はトップレベルに展開され、Docker由来のメタ情報(container_id / container_name / source)は meta.* にまとめられます。
.
├── log
│ ├── trocco-self-hosted-runner-enhanced
│ │ └── 20260424.jsonl
│ └── trocco-self-hosted-runner-normal
│ └── 20260424.jsonl
├── .env
├── .gitignore
├── daily_rotate.lua
├── docker-compose.yaml
├── fluent-bit-json-parser.conf
├── fluent-bit.conf
├── Makefile
└── README.md
なお、ログファイルを無制限に保持すると容量が膨らんでいくため、今回の構成では log-cleaner という補助コンテナを動かし、30日以上経過した .jsonl を1日1回自動削除しています。保持期間を変えたい場合は docker-compose.yaml の find ... -mtime +30 を調整してください。
log-cleaner:
image: alpine:latest
volumes:
- ./log:/log
entrypoint: ["/bin/sh", "-c"]
command: ["while true; do find /log -name '*.jsonl' -mtime +30 -delete; sleep 86400; done"]
restart: always
network_mode: none
この構成はあくまでサンプルなので、実運用の際には要件に合わせてログの設計をしてください。ローカルで保持する場合は、
- 保持したファイルをどのように保管/利用するのか
- ファイルの保存期間をどうするか
などが検討観点になります。また、そもそもローカルで保持するのかというのも論点です。クラウドへの連携もできるので、運用を考えるとそちらの方を取ることも有力な選択肢になるでしょう(fluent-bitの場合は fluent-bit.conf の [OUTPUT] を差し替えることでS3等への転送に切り替えられます)。
ちなみに、TROCCOのジョブという意味ではSaaSのTROCCOにもログは連携されていきますので、最低限そちらで確認する形でも大丈夫です。ただし、その場合はコンテナ側に問題があるときのログが確認できないことにはご留意ください。
イメージバージョンの調整
現在使われているバージョンを確認したい場合は、Self-Hosted Runnerのクラスター一覧から該当のクラスターを選択して表示してください。
過去にlatestタグで利用したことがある場合は、利用時の最新イメージがlatestタグに対応するものとして保持されています。その後に更新された新しいバージョンを利用したい場合は、新しいイメージを再度pullする必要があるので、以下のいずれかの対応をしてください。
docker-compose up -d --pull alwaysコマンドを実行する- 以下のコマンドを実行する
docker-compose downdocker image listdocker image rm <上記の一覧に出てくるID>
また、特定のバージョンを実行したい場合は、docker-compose.yamlで指定するタグを:latestではなく、:vX.X.Xの形で直接指定することもできます。
今回やっていないこと
上記とは別の観点として、
- docker-composeのプロセス自体の死活監視
- 稼働環境としてのサーバ/VMの監視
- サーバ/VMでのネットワークの監視
といったことも、場合によっては必要になってきます。コンテナ自体の管理と、コンテナの外部環境の管理の両側面から、要件に合わせて必要な対応をしてください。
おわりに
構成自体はシンプルなものですが、Runnerの仕組み自体がインバウンド通信を受けずにRunner主導で動くもののため、十分に稼働するものになります。コンテナ自体の起動/稼働数の変更/停止などを柔軟にやろうとするとクラウドサービスが望ましいかもしれませんが、構成の一つとしてご活用ください。






