まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2月も中旬に差し掛かろうとするこのタイミングで今年最初のブログエントリです。サボり癖を付けないようにしないと。。

久しぶりに phpMyAdmin を使ってみました。MySQL を使っている環境で開発していると、この手の GUI クライアントアプリが欲しくなる機会は多いはずです。アプリ開発でなくても WordPress のような CMS を使っている場合でも、こういった DB に直接アクセスできるツールがあるとちょっとしたデータ変更などにも便利ですよね:
2026021100


私自身は 10 年以上前に WordPress のカスタマイズをする機会が多かったこともあり、phpMyAdmin のヘビーユーザーでした。当時はまだ「コンテナ」の知識も乏しく、物理/仮想サーバーに直接 PHP や httpd 、そして phpMyAdmin をインストールして使ってました。今回はイメージの便利さを覚えたこともあり、モジュールを直接インストールするのではなく、DockerHub で見つけた公式 phpMyAdmin イメージで挑戦してみました(ついでに MySQL も DockerHub の公式イメージを使い、docker compose を使って構築してみました)。

【ただ普通に使う】
ただ普通に DockerHub の公式イメージを使って MySQL と phpMyAdmin を起動するならこのような docker-compose.yaml を用意するだけです:
version: '3'

services:
  db:
    image: mysql:5.7
    volumes:
      - ./mysql:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root_passw0rd
      MYSQL_DATABASE: mydb
      MYSQL_USER: myuser
      MYSQL_PASSWORD: mypass

  phpmyadmin:
    depends_on:
      - db
    image: phpmyadmin
    ports:
     - "3001:80"
    restart: always
    environment:
      - PMA_ARBITARY=1
      - PMA_HOSTS=mydb
      - PMA_USER=myuser
      - PMA_PASSWORD=mypass

上の例では MySQL の root パスワードを root_passw0rd、作成する DB 名は mydb 、アクセスするユーザーは myuser で、そのパスワードは mypass としています(同じユーザーとパスワードで phpMyAdmin からも接続しています)。なので phpMyAdmin で実行できる内容はこのユーザーに与えられた権限次第、ということになります。MySQL は ID とパスワードだけで接続できるよう、MySQL 5.7 を指定して利用します。

また docker compose 実行時のディレクトリに mysql/ というフォルダを作って、MySQL からボリュームマウントしています(MySQL コンテナを再起動してもデータが残るようにしています)。

また phpMyAdmin は 3001 番ポートから接続できるようポートフォワードしました。

この docker-compose.yaml をカレントディレクトリに用意し、mysql/ ディレクトリも作った上で以下のコマンドを実行すると MySQL と phpMyAdmin がコンテナとして起動し、3001 番ポートを指定してブラウザでアクセスすると phpMyAdmin の GUI にアクセスできます:
$ docker compose up -d

2026021101


ここまではさほど難しくありません。


【何が問題か?】
さて、上記までは特別な要素はなくて、一般的な docker-compose と MySQL, phpMyAdmin の知識だけで実現できる内容でした。ところがこのままでは都合悪いケースが結構あります。

なんといっても、URL だけで phpMyAdmin が利用できるということは「認証なしに(データベースのユーザーIDやパスワードを知らなくても)データの内容にアクセスできてしまう」ことになります。例えば MySQL 側であるデータベースに対する参照(SELECT)権限だけを持ったユーザーを作り、そのユーザー ID を docker-compose.yaml 内に記載すれば、phpMyAdmin はそのユーザー権限で使うことになるため、参照しかできない(データの作成や更新、削除はできない)という制約をかけることはできます。ただそれでも、パスワードに相当するセキュリティ要素無しにデータの内容が見れてしまうのは考え物です。

そういった場合の設定として、多くのケースでは phpMyAdmin を開く前に認証を求めるような設定を追加します。代表的な1つとして、ベーシック認証と呼ばれる ID とパスワードの組み合わせを追加するケースも珍しくありません。

ところが phpMyAdmin でベーシック認証を加えるには phpMyAdmin 側というよりも HTTP サーバー側にそのための設定が必要です。物理/仮想サーバーを使って MySQL や phpMyAdmin を用意した場合は同時になんらかの HTTP サーバーもインストールしているはずなので、その中にベーシック認証を使うための設定をすればいいのですが、今回のように phpMyAdmin のコンテナイメージを使っていると、ある程度コンテナの中身も理解していないと、そのためのカスタマイズが困難です。

以下はそのための、つまり phpMyAdmin の公式コンテナイメージを使って HTTP サーバー部分にベーシック認証を追加するための設定方法です。


【phpMyAdmin にベーシック認証をかける】
上述のように phpMyAdmin 公式コンテナでベーシック認証を有効にするには、コンテナイメージ内の HTTP サーバー部分でベーシック認証を有効にする必要があります。具体的には docker-compose.yaml 内で phpMyAdmin を利用する際の HTTP サーバーにベーシック認証を追加するようにイメージをビルドし直してからデプロイするようにカスタマイズします。

そのための手順を以下で紹介します。まず docker-compose.yaml と同じディレクトリにあらかじめ以下3つのファイルを用意しておきます:
・.htpasswd
・.htaccess
・Dockerfile.phpmyadmin

".htpasswd" ファイルはベーシック認証のユーザー ID とパスワードの組み合わせを定義するファイルです。具体的には Linux のターミナルで docker-compose.yaml と同じディレクトリに移動し、以下のコマンドを実行して作成します(実行するとパスワードの入力を求められるので、確認含めて2回パスワードを入力します):
$ htpasswd -c .htpasswd (ユーザーID)

仮にユーザー ID を "admin"、パスワードを "P@ssw0rd" とすると、以下のような内容の .htpasswd ファイルが作成されます(ユーザー ID である admin はそのまま表示されていますが、パスワード部分はエンコードされたものになります):
admin:$apr1$OaTHdA4B$vGmbnAxkB5b./q5zXPJHN.

もう1つのファイル .htaccess は以下の内容で用意します:
AuthType Basic
AuthName "Please enter your password"
AuthUserFile /var/www/html/.htpasswd
Require valid-user

"/var/www/html/.htpasswd" ファイルを参照してベーシック認証を実施し、その中に書かれたユーザーID/パスワードと一致しないと認証エラーになる、という内容です。

最後のファイル Dockerfile.phpmyadmin には phpMyAdmin コンテナのビルド内容を記述します。具体的には以下の内容そのままで用意します:
FROM phpmyadmin
COPY ./.htaccess /var/www/html/.htaccess 
COPY ./.htpasswd /var/www/html/.htpasswd

Dockerfile を書いたことがあると内容の意味も理解できると思います。これは「phpMyAdmin (公式コンテナイメージ)をベースに、(上で用意した).htaccess ファイルをコンテナの /var/www/html/.htaccess に、.htpasswd ファイルをコンテナの /var/www/html/.htpasswd ファイルにそれぞれコピーする」という内容が記述されています。

最後に docker-compose.yaml ファイルを一部書き換えて上記のビルドが実行されるようにします:
version: '3'

services:
  db:
    image: mysql:5.7
    volumes:
      - ./mysql:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root_passw0rd
      MYSQL_DATABASE: mydb
      MYSQL_USER: myuser
      MYSQL_PASSWORD: mypass

  phpmyadmin:
    depends_on:
      - db
    build: 
      context: ./
      dockerfile: Dockerfile.phpmyadmin
    image: phpmyadmin
    ports:
     - "3001:80"
    restart: always
    environment:
      - PMA_ARBITARY=1
      - PMA_HOSTS=mydb
      - PMA_USER=myuser
      - PMA_PASSWORD=mypass

docker-compose.yaml ファイルに上記赤字部分を追加します。ここでは「Dockerfile.phpmyadmin を使ってコンテナをビルドする」よう指示されています。

ここまでの変更を行って、再度 "docker compose up -d" を実行すると、今度は phpmyadmin コンテナをデプロイする前に上述のビルドが実施され、HTTP サーバーにベーシック認証が追加された状態でデプロイされます。

ここまでの手順が正しく実行されていれば、次に 3001 番ポートを指定してブラウザでアクセスすると、ベーシック認証が求められ、正しいユーザーIDとパスワード(上記例の場合は admin と P@ssw0rd)を入力しないと phpMyAdmin GUI は表示されません:
2026021102


最低限、このようなベーシック認証で守られていれば URL が分かってもデータの内容にはアクセスできなくなります。MySQL データを GUI で見ることができる phpMyAdmin は便利ですが、インターネットアクセスを許可する場合は最低限このような方法でデータを守る必要があります。最近はコンテナ環境を使うケースも増えていると思うので、そんな人の参考になれば。


毎年恒例のマンホールマップ年間アクセスランキングを発表します。 2025 年にマンホールマップでもっとも人気のあったマンホール蓋をベスト10形式で紹介します。 また今年新たに投稿された蓋の中で最も人気があった「新人賞」と、 今年最も多くの蓋画像を投稿いただいた方「最多投稿賞」を紹介します。

集計のルールとしては 2024/12/21 から 2025/12/20 までの集計期間における、 PC およびスマホのブラウザから単独ページとしてのアクセス数を集計しています。 ページビューとしての集計なので、例えば同じページの画面をリロードした場合は1回とだけカウントされます。

なお、過去11回の結果はこちらを参照ください:
2014 マンホールマップ年間アクセス数ランキング
2015 マンホールマップ年間アクセス数ランキング
2016 マンホールマップ年間アクセス数ランキング
2017 マンホールマップ年間アクセス数ランキング
2018 マンホールマップ年間アクセス数ランキング
2019 マンホールマップ年間アクセス数ランキング
2020 マンホールマップ年間アクセス数ランキング
2021 マンホールマップ年間アクセス数ランキング
2022 マンホールマップ年間アクセス数ランキング
2023 マンホールマップ年間アクセス数ランキング
2024 マンホールマップ年間アクセス数ランキング


では今年の各賞を発表します。


2025 最多投稿賞

2025 年は集計期間中に 408 枚ものマンホール画像が投稿されました。今年も多くの皆様からの投稿によってマンホールマップは支えていただきました。改めて投稿に協力いただいた皆様、ありがとうございました。

そして今年マンホールマップに最も多くの画像を投稿いただいたユーザーに与えられる賞、それが最多投稿賞です。 今年もコンスタントに投稿いただいた 42ER03 様が最多投稿でした(5年連続8回目)。 今年も感謝の限りでございます。 m(__)m

なお2位は carz82902686 様(3年連続)、 3位は私 dotnsf でした(3年連続)。上位投稿者が固定化されつつある状況は改善の余地というか、新しい施策の必要性がありそうに感じています。


2025 新人賞&総合ランキングベスト10

いよいよ 2025 年マンホールマップ年間アクセス数ランキングを発表する時がやってまいりました。 総合ランキング1位となる MVM(Most Variable Manhole) の座はどの蓋に!?


まずは新人賞です。今年投稿された中で最もアクセス数の多かった蓋を紹介します。こちらです:

市区町村投稿者画像
群馬県草津町 dotnsf


「♪草津よいとこ、一度はおいで」、で有名な群馬県草津町の湯畑周辺に設置された『ゆもみちゃん』デザインのマンホールが今年投稿された蓋の中では最も多いアクセスを記録していました。カタカナの「サ」が九つ描かれた草津町の町章も有名ですね。 

この蓋の投稿者は私(dotnsf)でした。実は2年連続新人賞でした。



では改めて 2025 年のランキングを発表します。まずは 10 ~ 4 位です。

順位昨年順位市区町村投稿者画像
10 - 東京都中央区 dotnsf


この年間アクセス数ランキングではたまに「なんでこの蓋の人気があったのか?」という理由がよくわからない蓋がランキング入りすることがあるのですが、いきなり解説の難しい蓋がなぜか10位でした。築地市場近く、浜離宮踏切跡のすぐ近くに設置された JIS マンホール蓋です。この真ん中のマークは旧東京市章と思われます。人気の秘密はそこ、、かな? 投稿者は私、dotnsf です。


第9位!

順位昨年順位市区町村投稿者画像
9 - 北海道泊村 minamu4545


北海道で唯一の原子力発電所を持つ泊(とまり)村、そのカブト岬が描かれたデザインマンホールです。漁業が盛んな地域らしく、漁船やカモメも描かれていますね。 投稿者はこのアクセスランキングでは常連となった minamu4545 様です。


第7位は同点でした。というわけで1つ目の第7位!

順位昨年順位市区町村投稿者画像
7 - 東京都葛飾区 42ER03


東京都葛飾区の地味な蓋が第7位でした。毎年のことではありますし、この後も続くのでこういってはアレですが、なぜこの地味な蓋に人気が・・・ 投稿者は最多投稿賞でもある 42ER03 様です。


もう一つの第7位!

順位昨年順位市区町村投稿者画像
7 - 静岡県静岡市 meaculpax3


静岡県静岡市のマンホール蓋ですが、より正確には「旧清水市」のマンホール蓋が第7位でした。この蓋のように市区町村合併によって現存しない自治体の足跡となる蓋には人気が集まりやすい傾向を感じます。投稿者は meaculpax3 様です。


第6位!

順位昨年順位市区町村投稿者画像
6 - 宮崎県宮崎市 my_pace_man


宮崎県宮崎市に設置された、市の花でもあるハナショウブがデザインされた蓋が第6位でした。"WELCOME TO MIYAZAKI" というメッセージは観光客向けですかね?投稿者は my_page_man 様です。ランキング入りは初じゃないかな?


第5位!

順位昨年順位市区町村投稿者画像
5 1 東京都台東区 minamu450


昨年第1位だった、東京都台東区の助六夢通り蓋が今年は第5位でした。一昨年は3位だったので、3年連続トップ10入りを果たしたことになります。浅草に設置されたこの蓋は外国人観光客にも人気ありそうだと思います。投稿者は minamu450 様です。


第4位!

順位昨年順位市区町村投稿者画像
4 - 東京都墨田区 42ER03


東京都墨田区に設置された東京都23区マンホールが第4位でした。 投稿者は 42ER03 様です。42ER03 様は第7位の蓋と併せて、地味なマンホールで2つのトップ10入りを果たしたことになりますね。





ここからはトップ3の発表です。第3位!!

順位昨年順位市区町村投稿者画像
3 - 東京都西東京市 minamu4545



東京都ですが23区や伊豆諸島以外としては珍しく、西東京市の蓋が 2025 年の第3位でした。ケヤキ、ハナミズキ、ヒマワリ、コスモス、スイセン、そしてツツジが描かれた美しいカラーマンホールです。 投稿者はminamu4545様(第9位で紹介した minamu450 様と同一人物)です。2年連続の3位獲得でした。


第2位!!!

順位昨年順位市区町村投稿者画像
2 - 岐阜県岐阜市 SatoMachiya




岐阜県岐阜市(旧柳津町)の旧町章がデザインされたハニカム柄の地味な蓋が今年の第2位となりました。上述しましたが、このような現存しない自治体の足跡がマンホールとして残っているのは地元の人にとっては嬉しいものですよね。なおこの画像はマンホールマップが生まれた 2011 年に投稿されたものでした。14年目にして初のランキング入りでした。投稿はこの年間ランキングでは常連の SatoMachiya 様です。

さて 2025 年の MVM はどの蓋に?  注目の第1位!!!!
























順位昨年順位市区町村投稿者画像
1 - 大阪府泉南郡田尻町 minamu4545


大阪府泉南郡田尻町のマスコットキャラクター『たじりっち』が描かれたデザインマンホールが 2025 年の MVM となりました。蓋の真ん中に大きく描かれているので他が目立たなくなっているのですが、よく見るとたじりっちの頭には田尻の特産品でもあるタコやコスモスが描かれており、また背景にはスカイブリッジ、ヨット、航空機といった田尻でよく見かける風景が描かれています(ちなみにたじりっち自身も泉州タマネギをモチーフにしています)。実は今年はミャクミャクマンホールの人気が高いのではと想像していたのですが、情報量の多いこのデザインマンホールが同じ大阪のライバルをぶっちぎっての MVM となりました。

投稿者である minamu4545 さんはこれで4年連続 MVM タイトルホルダーとなりました。人気蓋をかぎ分ける鋭い嗅覚をお持ちなのだと思います。 あらためておめでとうございます!!


ちなみに以前はアクセスランキングを席巻していた「ポケふた」、今年もランキングに入ることはありませんでした。 もうね、こういうのがマンホールマップのよく分からないところで、データサイエンス泣かせではあります。。


さて来年のマンホールマップランキングはどうなるのでしょう?
午年となる 2026 年も引き続きマンホールマップをよろしくおねがいします。




IBM Cloud から提供されている生成 AI の自動化ソリューションである watsonx Orchestrate の agent builder を使ってみました。システムプロンプトや RAG 、 MCP 、外部エージェントといった生成 AI の補助機能を簡単に使って独自エージェントを定義したり、定義したエージェントを外部公開したり、といったことが簡単に実現できる IBM の生成 AI ソリューションです(IBM Cloud アカウントがあると1か月間無料トライアルで使えます):
2025121801


ここで作成した自分のエージェントを REST API として公開することもできますが、より簡単な外部連携の方法として、小さなスクリプト(JavaScript)を HTML に貼り付けて、その HTML ページ内にチャット機能を埋め込む形でエージェントを使う、といったこともできます(embedded agent 機能と呼びます):
2025121802
(agent builder を使って作ったエージェントを・・)


2025121803
(外部ウェブページに貼り付けてチャットボットとして活用する)


この embedded agent を使うことで HTML 内を編集可能なウェブページであれば(WordPress のような CMS も含めて)作成したエージェントを使ったチャットボット機能を任意のページに埋め込むことができるようになります。比較的簡単な外部連携方法の1つです。


しかし、この embedded agent 機能を私が使おうとしたところうまく動きませんでした。そこで色々調べた所、IBM Cloud 版の watsonx Orchestrate では現状(2025年12月) embedded agent はデフォルト設定のままでは正しく動作しない、ということがわかりました。2026 年初頭のアップデートによって IBM Cloud 画面内からの設定変更が可能になるような予定もあるらしいのですが、現時点では REST API を呼び出す形で watsonx Orchestrate の設定内容を変更(Security を Disabled に変更)しないと embedded agent が使えない、ということが分かりました。

そのためのセキュリティ変更を curl と jq が導入された Linux/macOS 環境で実施するためのシェルスクリプトを独自に作って用意しました。以下の内容を set_watson_embed_security.sh として保存し、実行権限を付けてください:

#!/usr/bin/env bash
# set_watson_embed_security.sh
# IBM Cloud IAM トークンを取得し、Watson Orchestrate Embed Secure Config を更新する。
# Usage:
#   bash set_watson_embed_security.sh --api-key "(API キー)" --instance-id "(インスタンスID)" [--insecure]

set -euo pipefail

IAM_URL="https://iam.cloud.ibm.com/identity/token"
WATSON_BASE_URL="https://api.jp-tok.watson-orchestrate.cloud.ibm.com"
INSECURE_FLAG=""
API_KEY=""

INSTANCE_ID=""

log() { printf '[INFO] %s\n' "$*" >&2; }
err() { printf '[ERROR] %s\n' "$*" >&2; }

# 引数パース
while (( $# )); do
  case "$1" in
    --api-key)
      API_KEY="${2:-}"; shift 2 ;;
    --instance-id)
      INSTANCE_ID="${2:-}"; shift 2 ;;
    --insecure)
      INSECURE_FLAG="--insecure"; shift ;;
    -*)
      err "不明なオプション: $1"; exit 1 ;;
    *)
      err "不明な引数: $1"; exit 1 ;;
  esac
done

# 必須チェック
[[ -n "${API_KEY}" ]] || { err "API KEY が指定されていません。--api-key を指定してください。"; exit 1; }
[[ -n "${INSTANCE_ID}" ]] || { err "INSTANCE ID が指定されていません。--instance-id を指定してください。"; exit 1; }

# jq があるか確認(任意)
JQ_AVAILABLE=0
if command -v jq >/dev/null 2>&1; then
  JQ_AVAILABLE=1
fi

log "IBM Cloud IAM アクセストークンを取得中..."


# IAM トークン取得
# 参考のコマンド例:
# curl --fail -sS --insecure --request POST \
#   --url "https://iam.cloud.ibm.com/identity/token" \
#   --header "Content-Type: application/x-www-form-urlencoded" \
#   --data "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=(API KEY)"

IAM_RESP="$(curl --fail -sS ${INSECURE_FLAG} \
  --request POST \
  --url "${IAM_URL}" \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey=${API_KEY}" \
  || { err 'IAM トークン取得に失敗しました。API KEY が正しいか、ネットワーク/プロキシ/証明書を確認してください。'; exit 1; })"

# アクセストークン抽出
ACCESS_TOKEN=""
if [[ "${JQ_AVAILABLE}" -eq 1 ]]; then
  ACCESS_TOKEN="$(printf '%s' "${IAM_RESP}" | jq -r '.access_token // empty')"
else
  # jq が無い場合の簡易抽出(最低限の JSON 形式想定)
  ACCESS_TOKEN="$(printf '%s' "${IAM_RESP}" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]\+\)".*/\1/p')"
fi


if [[ -z "${ACCESS_TOKEN}" ]]; then
  err "アクセストークン抽出に失敗しました。応答: ${IAM_RESP}"
  exit 1
fi

log "アクセストークンの取得に成功しました。"

# Watson Orchestrate Embed Secure Config 更新
CONFIG_URL="${WATSON_BASE_URL}/instances/${INSTANCE_ID}/v1/embed/secure/config"
BODY='{"is_security_enabled": false}'

log "Watson Orchestrate の Embed Secure Config を更新します: is_security_enabled=false"
# ステータスコードを取得して判定

HTTP_CODE="$(curl -sS ${INSECURE_FLAG} \
  -X POST "${CONFIG_URL}" \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -d "${BODY}" \
  -w '%{http_code}' -o /tmp/watson_embed_resp.$$ || true)"

if [[ "${HTTP_CODE}" =~ ^2[0-9][0-9]$ ]]; then
  log "設定更新に成功しました。(HTTP ${HTTP_CODE})"
  printf '\n=== API 応答 ===\n'
  cat /tmp/watson_embed_resp.$$

  printf '\n===============\n'
  rm -f /tmp/watson_embed_resp.$$
else
  err "設定更新に失敗しました。(HTTP ${HTTP_CODE})応答ボディ:"
  printf '\n=== API 応答 ===\n'
  cat /tmp/watson_embed_resp.$$
  printf '\n===============\n'
  rm -f /tmp/watson_embed_resp.$$
  exit 1
fi

log "一連の処理が完了しました。"
exit 0

実行時は以下のように API キー(※1)と watsonx Orchestrate のインスタンス ID (※2)を指定します:
$ ./set_watson_embed_security.sh --api-key "(API キー)" --instance-id "(インスタンスID)"

※1※2いずれも IBM Cloud で watsonx Orchestrate インスタンスを選択した画面の Manage メニュー内 "Credentials" 部に記載されています。なおインスタンス ID は URL と書かれた文字列内の最後、"/instances/" に続くランダムに見える文字列部分がインスタンス ID です:
2025121901


これらの情報をコマンドラインで指定して実行すると、API キーからアクセストークンを取得して、セキュリティを無効化するための API が実行されます。

処理が成功すると「設定更新に成功しました。」「一連の処理が完了しました。」というメッセージが表示されます。また API 応答結果のJSON 文字が表示され、その最後に "is_security_enabled": false と表示されていればセキュリティの無効化に成功しています:
2025121902


現時点では、このセキュリティ無効化に成功できている状況下で watsonx Orchestrate の embedded agent が動きます:
2025121803


なお watsonx Orchestrate における「セキュリティの有効化/無効化」についてはこちらを参照ください:
https://developer.watson-orchestrate.ibm.com/manage/channels#enabling-security




このページのトップヘ