生成AIのGEMINIにアシストしてもらってQNAPに自分でビルドしたコンテナを導入する

ContainerStationのコンテナでは機能が足りない

前回記事で、QNAPのContainerStationで用意されているコンテナを導入する方法を解説しました。

www.toridelaw.com

しかしいろいろな機能を実現しようとすると、インストールされている機能が本当に必要最低限しかないことが判明し、ContainerStationで用意されているコンテナを利用することを放棄しました。機能が少ないため追加でソフトをインストールしようとすると、かなりトラブルが出て、必要とするソフトのインストールができないことが理由です。

調べてみると、必要な機能を最初から組み込んで構築(ビルド)して、必要な機能が実装されたコンテナを作成できるとわかったので、記録しておきます。

QNAPの機種はTVS-472XT(Intel CPU i7-8700T)、QTS 5.2.3.3006が前提です。

Intel/AMD CPUであれば後述する内容でのビルドからコンテナ立ち上げまで問題なくできるはずです。

さらっと書いていきますが、とにかく異様にトラブルが出て、ビルドしてコンテナを問題なく立ち上げてスクリプト等を実行させるのにかなり手間がかかっています。

QNAPで拡張フォルダー許可を有効にして、フォルダの権限設定をする

まず最初に、QNAPのコントロールパネルから、権限設定-共有フォルダー-拡張フォルダー許可を有効にする、にチェックを入れます

ちょっとややこしい話になりますが、QNAPでフォルダーやファイルを作成して権限を有しているユーザーと、コンテナ内でフォルダーやファイルを作成して権限を有しているユーザーがQNAPだと別々になります。

これが何を意味するかというと、コンテナ内でフォルダーやファイルを作成すると、QNAP側からは変更や削除ができないフォルダーやファイルが作成されるということです。

QNAPのContanerStationでコンテナ作成の際にフォルダーをマウントする設定があるのですが、何も考えずにマウントだけすると、QNAP側からは変更や削除ができないフォルダーやファイルが作成される現象が生じてむちゃくちゃ不便です。

www.qnap.com

この問題を解消するには、共有フォルダの権限(permission)を変更する必要がありますが、QNAPの初期設定では共有フォルダの権限(permission)を変更できません

共有フォルダの権限(permission)を変更するためには、先に拡張フォルダー許可を有効にする必要があります(リンク先は公式の2023年記事なのに、2025年時点で設定箇所が変わっているのはちょっとと思いますが。設定関係は安易に場所を変更しないでもらいたい)

QNAPのフォルダーをコンテナにマウントして、QNAP側からもコンテナ側からも読み書きできる場所を、FileStationで作っておきます。

コンテナは再ビルドすると仮想的に作成したフォルダー等が全部失われてしまいます。コンテナ内のスクリプト・プログラム・設定ファイルが再ビルドごとに失われてしまうので、QNAP側のフォルダーをマウントしてそのマウントしたフォルダー・ディレクトリに再ビルド後も残しておくスクリプト等を作成します。

コンテナにマウントする共有フォルダーで右クリックしてプロパティを表示させ、右側の権限タブをクリックすると、右下に+の追加ボタンがあるので+ボタンを押します。

左上のプルダウンメニューからローカルグループを選択して、everyoneの横にあるRW(読み書き)にチェックを入れてから、右下の追加ボタンを押します。

権限タブにeveryone 読み取り/書き込みが表示されているのを確認して、適用ボタンを押します。

これで、今後マウントするフォルダー以下はQNAP側からもコンテナ側からも読み書きが可能になります。

もし複数のフォルダーをマウントする場合は、マウントするフォルダー全てに同じ設定をしておきます。

WindowsからSSHでQNAPにログインする

使用しているWindows11マシンからQNAPにSSHでログインします。

Windows11だと、タスクバーのスタートメニューを右クリックすると出て切る一覧内のターミナルを実行して、

SSH QNAPユーザー名@IPアドレス

と入力してEnterキーを押すと、パスワード入力が促され、QNAPにログインすることができます。ログインするためのソフトをインストールしなくていいので、便利です。

コンテナ作成のためのフォルダーをQNAP内に作成する

コンテナを自分で作成してビルドするために、設定ファイル等を一カ所にまとめて置いておく必要があります。

以下は全てコマンドライン前提です。

以下の「ユーザー名」は全て使用する方のユーザー名に置き換えるなり、適宜置換してください。

引用部分はそれぞれひとかたまりなので、それぞれの引用部分を一行目冒頭から最終行文末までコピーして、コマンドラインに貼り付けるとそのまま動作します。

mkdir -p /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/drivers

dockerfilesを置くフォルダーを作成します。FileStationでフォルダーを作成してもいいのですが、作成した後にフォルダーの正確な位置をコマンドラインで把握する必要があるため、コマンドラインで作成した方がいいのではないかなと思います。

これも注意が必要で、/share/satou/とか作成すると、QNAP再起動の際に削除されるような記憶があります。必ずディスク(今回はCACHEDEV1_DATAがDISK1だった)の下に作成するようにしましょう。

コンテナ作成のためのファイルをQNAP内に作成する

以下、/share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/以下にファイルを作成していきます。この部分は、作成する方が実際の環境に合わせて、実際のフォルダー名に置換する必要があります。

cat > /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/password.txt << 'EOF'
ユーザー名:パスワード
EOF

まずコンテナ内で使用するユーザー名とパスワードを設定します。

ユーザー名はQNAPで使用しているユーザー名とまったく同じ名前にしましょう。

もしQNAPのユーザー名がsatouなら、コンテナ内で使用するユーザー名もsatouにするということです。これもQNAPとコンテナ内のユーザー名が別だとファイルやフォルダの扱いでトラブルが出るので、一致させておいた方が無難です。

cat > /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/cups.conf << 'EOF'
; /etc/supervisor/conf.d/cups.conf
[program:cupsd]
command=/usr/sbin/cupsd -f   ; CUPSをフォアグラウンドで実行
autostart=true             ; Supervisor起動時に自動開始
autorestart=true           ; 異常終了時に自動再起動
stdout_logfile=/var/log/supervisor/cupsd.log   ; 標準出力ログ
stderr_logfile=/var/log/supervisor/cupsd_err.log ; 標準エラーログ
user=root                  ; rootユーザーで実行
EOF

印刷関係機能を使用するのに必要なcupsの設定ファイルを作成します。

cat > /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/cupsd.conf << 'EOF'
#cat /etc/cups/cupsd.conf
#
# Configuration file for the CUPS scheduler.  See "man cupsd.conf" for a
# complete description of this file.
#

# Log general information in error_log - change "warn" to "debug"
# for troubleshooting...
LogLevel warn
PageLogFormat

SystemGroup lpadmin

# Specifies the maximum size of the log files before they are rotated.  The value "0" disables log rotation.
MaxLogSize 0

# Default error policy for printers
ErrorPolicy retry-job

# Only listen for connections from the local machine.
Listen *:631
Listen /run/cups/cups.sock

# Show shared printers on the local network.
Browsing No
BrowseLocalProtocols dnssd

# Default authentication type, when authentication is required...
DefaultAuthType Basic

# Web interface setting...
WebInterface Yes

# Timeout after cupsd exits if idle (applied only if cupsd runs on-demand - with -l)
IdleExitTimeout 60

# Restrict access to the server...
<Location />
  Order allow,deny
</Location>

# /etc/cups/cupsd.conf の該当部分の例

# Allow administration access from localhost and local network
<Location /admin>
  Order allow,deny
  Allow localhost
  Allow @LOCAL # ローカルネットワーク全体を許可する場合 (例: 192.168.10.*)
  # Allow 192.168.111.* # IP範囲指定の場合
  AuthType Default
  Require user @SYSTEM # @SYSTEM は SystemGroup (lpadmin) を指す
</Location>

# Allow configuration access from localhost and local network
<Location /admin/conf>
  AuthType Default
  Require user @SYSTEM # @SYSTEM は SystemGroup (lpadmin) を指す
  Order allow,deny
  Allow localhost
  Allow @LOCAL
  # Allow 192.168.111.*
</Location>

# Restrict access to log files...
<Location /admin/log>
  AuthType Default
  Require user @SYSTEM
  Order allow,deny
</Location>

# Set the default printer/job policies...
<Policy default>
  # Job/subscription privacy...
  JobPrivateAccess default
  JobPrivateValues default
  SubscriptionPrivateAccess default
  SubscriptionPrivateValues default

  # Job-related operations must be done by the owner or an administrator...
  <Limit Create-Job Print-Job Print-URI Validate-Job>
    Order deny,allow
  </Limit>

  <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job>
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  <Limit CUPS-Get-Document>
    AuthType Default
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  # All administration operations require an administrator to authenticate...
  <Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default CUPS-Get-Devices>
    AuthType Default
    Require user @SYSTEM
    Order deny,allow
  </Limit>

  # All printer operations require a printer operator to authenticate...
  <Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
    AuthType Default
    Require user @SYSTEM
    Order deny,allow
  </Limit>

  # Only the owner or an administrator can cancel or authenticate a job...
  <Limit Cancel-Job CUPS-Authenticate-Job>
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  <Limit All>
    Order deny,allow
  </Limit>
</Policy>

# Set the authenticated printer/job policies...
<Policy authenticated>
  # Job/subscription privacy...
  JobPrivateAccess default
  JobPrivateValues default
  SubscriptionPrivateAccess default
  SubscriptionPrivateValues default

  # Job-related operations must be done by the owner or an administrator...
  <Limit Create-Job Print-Job Print-URI Validate-Job>
    AuthType Default
    Order deny,allow
  </Limit>

  <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
    AuthType Default
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  # All administration operations require an administrator to authenticate...
  <Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
    AuthType Default
    Require user @SYSTEM
    Order deny,allow
  </Limit>

  # All printer operations require a printer operator to authenticate...
  <Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
    AuthType Default
    Require user @SYSTEM
    Order deny,allow
  </Limit>

  # Only the owner or an administrator can cancel or authenticate a job...
  <Limit Cancel-Job CUPS-Authenticate-Job>
    AuthType Default
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  <Limit All>
    Order deny,allow
  </Limit>
</Policy>

# Set the kerberized printer/job policies...
<Policy kerberos>
  # Job/subscription privacy...
  JobPrivateAccess default
  JobPrivateValues default
  SubscriptionPrivateAccess default
  SubscriptionPrivateValues default

  # Job-related operations must be done by the owner or an administrator...
  <Limit Create-Job Print-Job Print-URI Validate-Job>
    AuthType Negotiate
    Order deny,allow
  </Limit>

  <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
    AuthType Negotiate
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  # All administration operations require an administrator to authenticate...
  <Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
    AuthType Default
    Require user @SYSTEM
    Order deny,allow
  </Limit>

  # All printer operations require a printer operator to authenticate...
  <Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
    AuthType Default
    Require user @SYSTEM
    Order deny,allow
  </Limit>

  # Only the owner or an administrator can cancel or authenticate a job...
  <Limit Cancel-Job CUPS-Authenticate-Job>
    AuthType Negotiate
    Require user @OWNER @SYSTEM
    Order deny,allow
  </Limit>

  <Limit All>
    Order deny,allow
  </Limit>
</Policy>
EOF

印刷関係機能を使用するのに必要なcupsのデーモンの設定ファイルも作成しておきます。

cat > /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/Dockerfile << 'EOF'
# ベースイメージを指定 (Ubuntu 22.04)
FROM ubuntu:22.04

# 環境変数を設定 (タイムゾーン, ロケール)
ENV TZ=Asia/Tokyo
ENV LANG=ja_JP.UTF-8
ENV LANGUAGE=ja_JP:ja
ENV LC_ALL=ja_JP.UTF-8
ENV DEBIAN_FRONTEND=noninteractive

# 必要なAPTパッケージをインストール
RUN dpkg --add-architecture i386 && \
    apt-get update && \
    apt-get install -y --no-install-recommends \
    # 基本ツール
    apt-utils bash sudo nano less curl wget net-tools iputils-ping passwd rsyslog \
    # ロケール・タイムゾーン
    locales tzdata \
    # Python
    python3 python3-pip \
    # 開発・ユーティリティ
    git cron passwd supervisor libimage-exiftool-perl \
    # OCR・TIFF変換
    tesseract-ocr libtesseract-dev tesseract-ocr-jpn ocrmypdf unpaper ghostscript poppler-utils \
    # CUPS関連
    cups cups-client libcups2-dev libusb-0.1-4 \
    # ドライバー (Brother用など)
    lib32stdc++6 libc6-i386 \
    # ネットワーク探索 (オプション)
    libnss-mdns avahi-daemon \
    # ... 他のパッケージ ...
    libmagic1 \
    # その他必要に応じて
    && ln -fs /usr/share/zoneinfo/$TZ /etc/localtime \
    && echo $TZ > /etc/timezone \
    && locale-gen ja_JP.UTF-8 \
    && rm -rf /var/lib/apt/lists/*

# ユーザーとグループを作成 (UID: 1000, GID: 100 - ホストに合わせる)
RUN GROUP_NAME=everyone && \
    GROUP_ID=100 && \
    USER_ID=1000 && \
    USER_NAME=ユーザー名 && \
    if ! getent group ${GROUP_ID} > /dev/null; then groupadd -g ${GROUP_ID} ${GROUP_NAME}; fi && \
    useradd -m -u ${USER_ID} -g ${GROUP_ID} -s /bin/bash ${USER_NAME} && \
    chown ${USER_ID}:${GROUP_ID} /home/${USER_NAME}

# ★ lpadmin グループ作成 (存在しない場合のみ作成)
RUN groupadd lpadmin || true

# ロケール設定 (変更なし)
RUN locale-gen ja_JP.UTF-8 && update-locale LANG=ja_JP.UTF-8

COPY password.txt /tmp/password.txt

# ユーザー作成・設定 (useraddのエラーコード9を無視するように修正)
RUN (useradd -m -u 1000 -g 100 -s /bin/bash ユーザー名 || [ $? -eq 9 ]) && \
    echo "ユーザー名:$(cat /tmp/password.txt)" | chpasswd && \
    usermod -aG sudo ユーザー名 && \
    usermod -aG lpadmin ユーザー名 && \
    rm /tmp/password.txt


# ユーザー名 を sudoers に追加 (専用ファイルを使用)
RUN USER_NAME=ユーザー名 && \
    echo "${USER_NAME} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/${USER_NAME}_nopasswd && \
    chmod 0440 /etc/sudoers.d/${USER_NAME}_nopasswd


# 作業ディレクトリを設定
WORKDIR /home/${USER_NAME}

# Python の依存関係をインストール
COPY requirements.txt .
RUN pip3 install --no-cache-dir --upgrade pip && \
    if [ -f requirements.txt ]; then pip3 install --no-cache-dir -r requirements.txt; fi

# ★ マウントポイントとなるディレクトリを作成し、所有者をユーザー名に設定 ★
# /etc/cron.d は root 所有のままとするため除外
# ディレクトリを作成し、所有者を ユーザー名 と users グループに変更
RUN mkdir -p /home/ユーザー名/scripts /home/ユーザー名/disk \
    && chown ユーザー名:users /home/ユーザー名/scripts /home/ユーザー名/disk

# --- 設定ファイルのコピー ---
# ★ 修正した CUPS 設定ファイル (cupsd.conf) をコピー
COPY cupsd.conf /etc/cups/cupsd.conf

# Supervisord 設定ファイルをコピー
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# cron の設定ファイルをコピー
# この Dockerfile があるディレクトリに crontab を置いておく必要あり
#COPY crontab /etc/cron.d/ユーザー名-cron
# ユーザー名のcronとして設定する場合(ファイル内でユーザー指定が必要)
# ★★★ 削除:cronの書き換えが必要になるので、/share/CACHEDEV1_DATA/scripts/cron.d:/etc/cron.dでマウントしている。scripts/cron.d/ユーザー名-cronを書き換える ★★★

# ★★★ 追加: Supervisord のログディレクトリを作成 ★★★
RUN mkdir -p /var/log/supervisor && chown root:root /var/log/supervisor

# スクリプトやログ、データ用ディレクトリを作成し、所有者を変更
RUN mkdir -p ユーザー名/scripts/logs ユーザー名/disk && \
    chown -R 1000:100 ユーザー名/scripts ユーザー名/disk

# USER ${USER_NAME}
# Supervisordがrootで起動し、プログラムごとにユーザーを指定

# Supervisord をフォアグラウンドで起動
# Supervisord をフォアグラウンドで起動 (メイン設定ファイルを指定)
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
EOF

Ubuntu 22.04をベースに、必要な各種機能を組み込んでいます。

「UID: 1000, GID: 100 - ホストに合わせる」という記述がありますが、この設定をしないとQNAP側で作成したフォルダーやファイルをコンテナ内から変更したりすることができません

QNAPにログインした状態でIDコマンドを使用すると、UIDとGIDが取得できます。

    GROUP_ID=100 && \
    USER_ID=1000 && \

この部分は、IDコマンドを使用して取得したUIDとGIDの数字と同一にします

また、Dockerfile内では、#から始まるコメントを実行させる命令と同一行の後に書くとビルドに失敗します

具体的には、

# ★ 修正した CUPS 設定ファイル (cupsd.conf) をコピー
COPY cupsd.conf /etc/cups/cupsd.conf

# Supervisord 設定ファイルをコピー
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

とコメントを#を文頭にして記述することは許されますが、

COPY cupsd.conf /etc/cups/cupsd.conf # ★ 修正した CUPS 設定ファイル (cupsd.conf) をコピー

と、一行の中に命令の後に#を入れてコメントを書くことは許されません(ビルド時にエラーで止まる)。このコメントの書き方、GEMINI 2.5 Proで割と生成されがちなので、注意ポイントです。

それとビルド時に必要な機能を毎回ダウンロードする設定にしているので、普通のビルドよりも時間がかかるようです。これも以前ビルドしたときのデータを再利用する設定にしていたらトラブルが起きたので、安定的にビルドするため時間がかかってもやむをえないという判断で毎回新規取得する設定にしています。

cat > /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/supervisord.conf << 'EOF'
[supervisord]
nodaemon=true                ; supervisord をフォアグラウンドで実行 (必須)
user=root                    ; supervisord 自体は root で実行


[program:cron]
command=cron -f
user=root
autostart=true
autorestart=true
priority=10
# ★ ログファイルのパスを変更 ★
stdout_logfile=/home/ユーザー名/scripts/logs/cron_jobs.log
# ★ 必要ならログローテーションを設定 ★
stdout_logfile_maxbytes=10MB   ; 例: 10MBでローテーション
stdout_logfile_backups=5     ; 例: 5世代保持
# ★ エラー出力も同じファイルにまとめるか、別ファイルにするか選択 ★
redirect_stderr=true           ; エラー出力を標準出力と同じファイルへ (推奨)
# または stderr_logfile=/home/ユーザー名/scripts/logs/cron_jobs.err.log など

environment=HOME="/home/ユーザー名",USER="ユーザー名"
# ... その他の設定 ...
# /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/supervisord.conf に追記

[program:rsyslog]
command=/usr/sbin/rsyslogd -n  ; rsyslogd をフォアグラウンドで起動 (-n オプション)
autostart=true               ; Supervisor起動時に自動開始
autorestart=true             ; 予期せず終了した場合に自動再起動
user=root                    ; rsyslog は通常 root で実行
priority=50                  ; 起動優先度 (他のプロセスより先に起動させたい場合、小さい数字)

; rsyslogd 自体の標準出力・標準エラーのログファイル (rsyslog が管理するログとは別)
stdout_logfile=/var/log/supervisor/rsyslogd_stdout.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=3
stderr_logfile=/var/log/supervisor/rsyslogd_stderr.log
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=3

; --- CUPSデーモンの設定 ---
[program:cupsd]
command=/usr/sbin/cupsd -f  ; CUPSデーモンをフォアグラウンドで起動
priority=100              ; 優先度 (小さい数値が先に起動)
autostart=true
autorestart=true          ; CUPSデーモンは常に再起動させる
stdout_logfile=/home/ユーザー名/scripts/logs/cupsd.log
stderr_logfile=/home/ユーザー名/scripts/logs/cupsd_err.log
user=root

EOF

supervisordは、OSであるUbuntu立ち上げ時にユーザーが指定したプログラムを立ち上げてくれたりするソフト、くらいの認識です。プロセス監視ツールということのようですが、詳しいことは検索していただければ。

設定した日時にプログラムを自動実行してくれる機能などを入れています。

cat > /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/requirements.txt << 'EOF'
google-api-python-client
google-auth-httplib2
google-auth-oauthlib
google-generativeai
python-dateutil
requests
python-dotenv
discord.py
nest_asyncio
watchdog
googlemaps
pandocfilters
google-auth
ocrmypdf
openpyxl
todoist-api-python
python-docx
pandas
docxtpl
Pillow==9.5.0
python-magic
psutil
pytz
pynacl
httpx
APScheduler
pdf2image
jaconv
pypdf
xlwt
PyMuPDF
mojimoji
EOF

後日説明する、pythonという言語でいろいろ便利なスクリプトを実行するのに必要な機能をインストールする設定です。

cat > /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/run_container.sh << 'EOF'
#!/bin/bash

CONTAINER_NAME="qnap-ユーザー名-container"
IMAGE_NAME="qnap-ユーザー名-ubuntu"
# Dockerfile からビルドしたイメージ名

# 既存のコンテナを停止・削除 (エラーを無視)
echo "Stopping existing container '${CONTAINER_NAME}' (if any)..."
docker stop ${CONTAINER_NAME} >/dev/null 2>&1 || true
echo "Removing existing container '${CONTAINER_NAME}' (if any)..."
docker rm ${CONTAINER_NAME} >/dev/null 2>&1 || true

echo "Starting new container: ${CONTAINER_NAME} (using supervisord)"

# 新しいコンテナを起動 (root で起動)
# 各行末の \ の直後にスペース等がないか、最後の行に \ がないか確認
docker run -d --restart unless-stopped \
  -v /share/CACHEDEV1_DATA/scripts/:/home/ユーザー名/scripts \
  -v /share/CE_CACHEDEV2_DATA/disk:/home/ユーザー名/disk \
  -v /share/CACHEDEV1_DATA/scripts/cron.d:/etc/cron.d:rw \
  --name ${CONTAINER_NAME} \
  ${IMAGE_NAME}

# 起動確認 (任意)
sleep 2
# コンテナ起動のための短い待機
if [ "$(docker ps -q -f name=${CONTAINER_NAME})" ]; then
  echo "Container '${CONTAINER_NAME}' started successfully."
  echo "Use 'docker logs ${CONTAINER_NAME}' to view logs (Supervisord/Cron output)."
  echo "Use 'docker exec -it ${CONTAINER_NAME} bash' to get a shell inside the container."
else
  echo "Error: Failed to start container '${CONTAINER_NAME}'. Check Docker daemon logs." >&2
  exit 1
fi

exit 0
EOF

既存のコンテナを停止・削除して、新しくビルドしたコンテナを起動させる設定です。

「-v /share/CACHEDEV1_DATA/scripts/:/home/ユーザー名/scripts \」のところ(上の例では三カ所)は、「QNAPのフォルダー名:コンテナ内でQNAPのフォルダーを仮想的に設置するフォルダー名」で設定してください。

上記のように複数設置することもできますし、1個だけ設置することもできます。

仮想的に設置するフォルダー名は、「home/ユーザー名/~~」と、homeの下に設定するのが無難です。

ここまでで、ビルドに必要なファイルは揃いました。

# イメージをビルド
docker build --no-cache -t qnap-ユーザー名-ubuntu /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/ && \
# run_container.sh に実行権限を付与
chmod +x /share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/run_container.sh && \
# コンテナを実行 (初回実行時、または設定変更後)
/share/CACHEDEV1_DATA/dockerfiles/qnap-ユーザー名-ubuntu/run_container.sh

この引用部分をコマンドラインに貼り付けると、コンテナのビルド・ビルド用スクリプトの実行権限付与・コンテナ立ち上げまで一気に進みます。結構な時間が必要なので、その間は他のことでもしていましょう。

docker exec -it -u ユーザー名 qnap-ユーザー名-container /bin/bash

ビルドが完了してコンテナが実行されると、コマンドラインでコマンド入力ができるようになります。

そこでこの引用部分を貼り付け実行すると、QNAPのコマンドラインからコンテナにログインすることができます。引き続きコマンドラインですが。

ビルドは大変ですね

先回りしてトラブルを潰した上で、コンテナが問題なく動く設定を記述しましたが、ビルドできないとかコンテナがうまく立ちあがらないとかは何度となく発生し、安定したコンテナを作るまでが一苦労でした。

とはいえ、うまく動かないときは全部GEMINI 2.5 Proに聞くと(ほぼ)完璧な問題解決方法を提示してくれるので、根気よく進めていけば確実に改善されていくため、だいぶ助かりました。OSやプログラムのような閉じた環境でルール外のことは起きないというシロモノは、AIがめっぽう強い分野です。

初心者がいろいろ教えてもらう相手としては、説明が丁寧だし、いつ聞いてもいいし、しつこく聞いても絶対怒られないし、何度聞いても繰り返しであっても機嫌悪くならないので、AIは向いてるんだろうなぁと思います。