2026年5月31日

2026年5月31日

Dockerでcronを使って定期実行する方法|ホストのcron・cron専用コンテナ・WP-Cronの置き換え

Docker で動かしているアプリケーションで、データベースのバックアップやログの整理などを毎日決まった時間に実行したいことがあります。Docker 自体には定期実行の機能がないため、cron と組み合わせて使います。

ただし、通常の Linux サーバーと同じつもりでコンテナの中に cron を入れ、service cron start で起動しても、うまく動きません。コンテナは「最初に起動した1つのプロセス(PID 1)」が動いている間だけ動き続け、systemd などの仕組みもないためです。この記事では、Docker で定期処理を動かす方法を3つに分けて説明します。コマンドの確認には、2026年10月時点の Debian 12・Alpine 3.22 のイメージと Docker Compose v2 を使っています。

方法の選び方

方法 向いている場面
1. ホストの cron から docker compose exec などで実行する サーバー(ホスト)の crontab を編集できる。いちばん手軽で、コンテナを変更しなくてよい
2. cron を動かす専用のコンテナを作る ホストを触らずに、Compose の構成だけで定期処理まで完結させたい
3. WordPress の WP-Cron をサーバーの cron から呼ぶ Docker の WordPress で、予約投稿や定期処理を時間どおりに動かしたい

方法1: ホストのcronからコンテナでコマンドを実行する

ホストの cron から、動いているコンテナの中でコマンドを実行します。例として、Docker Compose で動かしている MariaDB のデータベースを毎日バックアップします。

compose.yaml は次のような構成とします(サービス名は db)。

services:
  db:
    image: mariadb:11
    environment:
      MARIADB_ROOT_PASSWORD: change-me
      MARIADB_DATABASE: wordpress
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

バックアップ用のスクリプトをホストに作ります(例: /usr/local/bin/wp-db-backup.sh)。

#!/bin/bash
set -euo pipefail

cd /srv/wordpress                 # compose.yaml がある場所
backup_dir=/srv/backup
mkdir -p "$backup_dir"
file="$backup_dir/wordpress-$(date +%Y%m%d-%H%M).sql.gz"

# 失敗したときに壊れたファイルが残らないよう、一時ファイルに書いてから名前を変える
docker compose exec -T db sh -c 'exec mariadb-dump -uroot -p"$MARIADB_ROOT_PASSWORD" --single-transaction "$MARIADB_DATABASE"' \
  | gzip > "$file.tmp"
mv "$file.tmp" "$file"

# 7日より古いバックアップを削除する
find "$backup_dir" -name 'wordpress-*.sql.gz' -mtime +7 -delete
  • docker compose exec の -T は、端末(TTY)を割り当てないオプションです。cron には端末がないので、cron から実行するときは付けます
  • パスワードはコンテナの環境変数 MARIADB_ROOT_PASSWORD を、コンテナの中のシェルで展開しています。スクリプトにパスワードを書かずに済みます
  • set -euo pipefail により、docker compose exec が失敗したらスクリプトがエラーで止まり、mv は実行されません(失敗時は .tmp ファイルが残ります)

実行権限を付け、一度手で実行して動くことを確かめます。

sudo chmod 755 /usr/local/bin/wp-db-backup.sh
sudo /usr/local/bin/wp-db-backup.sh
ls -l /srv/backup

動いたら、root の crontab に登録します(sudo crontab -e)。

# 毎日3時30分にバックアップし、結果をログに残す
30 3 * * * /usr/local/bin/wp-db-backup.sh >> /var/log/wp-db-backup.log 2>&1

cron の中では、ふだんのシェルより PATH が短く、環境変数も読み込まれません。スクリプトは / から始まるフルパスで指定し、うまく動かないときは which docker で調べた docker のフルパス(例: /usr/bin/docker)をスクリプトの中で使います。また、crontab の行に直接 % を書くと改行として扱われるため、date +%Y%m%d のようなコマンドはこの例のようにスクリプトの中に書きます。

コンテナを使い捨てで実行したい場合は、docker compose exec の代わりに docker compose run --rm サービス名 コマンド を使います。

方法2: cronを動かす専用のコンテナを作る

ホストの crontab を使わずに定期処理をしたい場合は、cron を フォアグラウンドで PID 1 として動かす コンテナを作ります。cron がコンテナの主プロセスになるので、cron が動いている間コンテナも動き続けます。

Debian系イメージの場合

Debian の cron パッケージを入れ、cron -f(フォアグラウンドで動かす)で起動します。

FROM debian:12-slim

RUN apt-get update \
 && apt-get install -y --no-install-recommends cron tzdata \
 && rm -rf /var/lib/apt/lists/*

COPY backup-job.sh /usr/local/bin/backup-job.sh
COPY mycron /etc/cron.d/mycron
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod 0755 /usr/local/bin/backup-job.sh /usr/local/bin/entrypoint.sh \
 && chmod 0644 /etc/cron.d/mycron

CMD ["/usr/local/bin/entrypoint.sh"]

/etc/cron.d/ に置くファイル(ここでは mycron)は、次の形で書きます。

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# 毎日1時に実行し、出力をコンテナのログ(docker logs)に送る
0 1 * * * root /usr/local/bin/backup-job.sh > /proc/1/fd/1 2> /proc/1/fd/2

/etc/cron.d/ のファイルには、次の決まりがあります。

  • 時刻の後に、実行するユーザー(ここでは root)を書く。crontab -e で編集する個人の crontab とは書式が違います
  • ファイルの権限は 0644 にし、所有者は root にする。グループや他人が書き込めるファイルは無視されます
  • ファイル名に . を含めない(Debian の cron は、. を含む名前のファイルを読みません)
  • 最後の行の後に改行を入れる

cron から実行したジョブの出力は、そのままでは docker logs に表示されません。docker logs に出るのは PID 1 の標準出力と標準エラーだけなので、上の例のように /proc/1/fd/1(PID 1 の標準出力)と /proc/1/fd/2(標準エラー)に送ります。

もう1つの注意点は、Debian の cron は、コンテナに渡した環境変数をジョブに引き継がないことです。docker run -e や Compose の environment で渡した値を使うジョブでは、起動時に /etc/environment へ書き出してから cron を起動します(Debian の cron は PAM の設定でこのファイルを読みます)。

#!/bin/sh
# entrypoint.sh: ジョブで使う環境変数を cron に引き継ぐ
printenv | grep -E '^(APP_ENV|TZ|DB_HOST|DB_NAME)=' > /etc/environment
exec cron -f

grep の条件には、ジョブで使う変数の名前を並べます。パスワードなどを /etc/environment に書くとコンテナの中のファイルに残るので、扱いに注意してください。

Alpine系イメージの場合

Alpine には、BusyBox の crond が最初から入っています。追加のパッケージは不要で、crontab は /etc/crontabs/root に書きます(ユーザーの欄は書きません)。

FROM alpine:3.22

RUN apk add --no-cache tzdata
COPY backup-job.sh /usr/local/bin/backup-job.sh
COPY crontab /etc/crontabs/root
RUN chmod 0755 /usr/local/bin/backup-job.sh

# -f: フォアグラウンドで動かす  -l 2: ログの詳しさ
CMD ["crond", "-f", "-l", "2"]
0 1 * * * /usr/local/bin/backup-job.sh

BusyBox の crond では、筆者が Alpine 3.22 で確認した範囲では、ジョブの出力がそのまま docker logs に表示され、コンテナの環境変数もジョブに引き継がれました。Debian の cron とは動きが違うので、ベースイメージを変えるときは注意してください。

タイムゾーンとComposeでの起動

コンテナの時計は、初期状態では UTC です。cron の時刻を日本時間で書きたい場合は、上の例のようにイメージに tzdata を入れ、環境変数 TZ を指定します。Debian・Alpine とも、この設定でジョブが日本時間どおりに実行されることを確認しました。

services:
  cron:
    build: ./cron            # Dockerfile などを置いたフォルダー
    environment:
      TZ: Asia/Tokyo
      APP_ENV: production
    restart: unless-stopped
docker compose up -d --build cron
docker compose logs -f cron

restart: unless-stopped を指定すると、サーバーの再起動後もコンテナが自動で起動し、cron も一緒に動き始めます。旧版の記事にあった「init システムを入れて再起動に備える」設定は不要です。

方法3: WordPressのWP-Cronをサーバーのcronから呼ぶ

WordPress には WP-Cron という定期処理の仕組みがありますが、これはサイトにアクセスがあったときに実行される仕組みです。アクセスが少ないと予約投稿などが遅れ、多いと処理の負担になります。Docker で動かしている WordPress でも、サーバーの cron から決まった間隔で呼ぶように変えられます。

まず、アクセスをきっかけに WP-Cron を実行しないようにします。公式の wordpress イメージが作った wp-config.php を使っている場合は、環境変数 WORDPRESS_CONFIG_EXTRA で設定を追加できます。

services:
  wordpress:
    image: wordpress:php8.3-apache
    environment:
      WORDPRESS_CONFIG_EXTRA: |
        define( 'DISABLE_WP_CRON', true );

自分で wp-config.php を管理している場合は、そのファイルに define( 'DISABLE_WP_CRON', true ); を書きます。

次に、ホストの cron から wp-cron.php を定期的に呼びます。WordPress の開発者向けドキュメントでは wget で wp-cron.php を呼ぶ例が紹介されています。curl を使う場合は次のとおりです(URL は自分のサイトに置き換えます)。

# 5分ごとに WP-Cron を実行する
*/5 * * * * curl -fsS -o /dev/null https://example.com/wp-cron.php

DISABLE_WP_CRON は「ページの表示をきっかけに実行する」動きを止めるだけで、wp-cron.php を直接呼べば処理は実行されます。WP-CLI を使える環境なら、wp cron event run --due-now で実行する方法もあります。詳しくは WP-CLIのcronコマンドの使い方 を参照してください。

動かないときの確認

  • ジョブが実行されない(Debian):/etc/cron.d/ のファイルの権限が 0644 か、ファイル名に . が入っていないか、ユーザーの欄があるか、最後の行に改行があるかを確認します
  • 時刻がずれる:コンテナの中で date を実行し、UTC のままになっていないか確認します。TZ を指定しても変わらない場合は、tzdata が入っているか確認します
  • 手で実行すると動くが、cron からだと失敗する:環境変数か PATH の違いが原因のことが多いです。Debian の cron では、上で説明した /etc/environment への書き出しを確認します
  • docker logs に何も出ない:Debian の cron では、ジョブの出力を /proc/1/fd/1 と /proc/1/fd/2 に送っているか確認します
  • ホストの cron から docker compose exec が失敗する:-T を付けているか、compose.yaml のあるフォルダーに cd しているか、cron のログ(Ubuntu では journalctl -u cron)を確認します

まとめ

  • コンテナの中で service cron start を実行しても cron は動き続けない。cron を使うなら、フォアグラウンドで PID 1 として動かす
  • ホストの crontab を使えるなら、docker compose exec -T で実行する方法がいちばん手軽
  • 専用のコンテナでは、Debian は cron -f、Alpine は crond -f で起動する。Debian の cron は環境変数を引き継がず、出力も /proc/1/fd/1 に送る必要がある
  • 時刻を日本時間で扱うには tzdata を入れて TZ を指定する
  • Docker の WordPress では、DISABLE_WP_CRON を設定し、ホストの cron から wp-cron.php を呼べる

関連記事:

お気軽にご相談ください

お見積りへ お問い合わせへ
▲