😎

dockerコンテナ内部でdebugpyで実行しVSコードからアタッチでのリモートデバッグの話

に公開

はじめに

通常はVSコードでlaunch.jsonの「type: "debugpy" or "python"」の記載をして
launch方式でデバッガーを起動すると思いますが、自分のPCや、ホスト側で動作させての
デバッグ実行のやり方です。
★ 上記が、みんなが知ってるやり方  ★
★ Python 3.x でも 2.x でも可能。ポイントは debugpy と VS Code 拡張の互換バージョンを揃えること ★
★ じゃ、この記事で書かれていることは、それと何が違うの? ★
★ この記事の目的は、一体、何 ? ★
が、真っ先に気になると思います。
当記事内の
pythonのみんなが知ってる通常的なデバッグと何が違うのかのお話
に詳しく、違いや、目的を書きました。
違いや、目的が先に知りたい人は、先に読んでください。

違いや、目的を簡単に言うと、
当記事はデバッグしたい対象のプログラムの動作は、
dockerコンテナ側ではあるが、デバッガーはホスト側のVSコードで行う
リモートデバッグの話です。
業務の中で、dockerコンテナ内のpython2.7.5での環境でする必要があり
そこで得たことを記事にしてます。ただし、やり方はpython3.xでも使える話
なので情報としての価値はあります。
もっとも、別の方法として、Dev Containers(旧称 Remote-Containers)を使うやり方もあります
Dev Containersは、VSコードをdockerコンテナ内部にログインする状況になります。
VSコードがdockerコンテナ側にログインした状況となり、launch方式の
デバッグ実行をすると、dockerコンテナ内部で対象のプログラムをデバッグ実行する。
コンテナの中だけで、デバッグ実行が完結する。
( ホストとコンテナ間のリモートデバッグではありません。)
Dev Containerはホスト側の自分のPCだけでデバッグ実行してるのと同様の事を
コンテナ側の中だけで行えるため、デバッガーがシンプルになります。
なお、
WSL2でのDev Containers(旧称 Remote-Containers)の話は、以前
https://zenn.dev/tazzae999jp/articles/b60d353fd9329d
の記事を書きました
ただし、Dev Containersは環境作るのは、それなりに手間なため、
そこまでしなくてもよい場合は、当記事のノウハウでのリモートデバッグのやり方でよいでしょう。

当記事は、
https://zenn.dev/tazzae999jp/articles/14073b30d62d95
の記事の続編として当記事を書きました

発端となった環境構築は下記のとおり

2025/09の下旬の話である。
本番環境が古めのLinuxでpython 2.7.5のシステムの保守開発のため、
ローカルにその環境を再現する環境を作った

開発環境としてdocker compose環境を構築した
本番はRed HatだったがDockerHub公開イメージがないため、
ベースになってるCentOSの対応するバージョンでのイメージを使った

dockerコンテナにpython2.7.5の古めのバージョンが入ってる環境で、
WSL2にRemote WSLで接続しているVSコードをホスト側として
リモートデバッグできる環境を構築した

上記の環境の「docker compose」のDockerfileやdocker-compose.ymlは、
当記事内の
発端の環境構築での「docker compose」を参考情報として書いた
で参照できます。

前記事の
https://zenn.dev/tazzae999jp/articles/14073b30d62d95
で、
該当の開発案件のプロジェクトを開いた時だけ、
ms-python.python-2021.9.1246542782
の拡張機能にバージョン固定した状況よりの続きの話である

いろいろと失敗しまくったが・・

いろんな方法を試して、失敗しまくりましたが・・・
下記の方法で、なんとかうまくいく方法を見つけました。

うまくいった方法

先に概要だけ示すと、

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
dockerコンテナ側にdebugpyインストールする
python -m debugpy --listen 0.0.0.0:5678 --wait-for-client hoge.py
の形式で、debugpyを通じた起動をする
すると、一旦、固まる
この固まる現象は正常動作である
VSコードのデバッガーを起動し、このプロセスにアタッチすると、
この固まりが解放され処理が続行する
この時、VSコード側で該当のソースコードにブレイクポイントを置いていなければ
そのまま、最後まで処理が完了するだろう
ブレイクポイントを置いていれば、そこで止まって
リモートデバッグとして、ステップ実行や、各種変数値の確認や、コードのイミディエイト実行など
通常のVSコードのデバッガー機能を活用した実行が行える

補足)
python -m debugpy --listen ... [--wait-for-client] script.py
は公式推奨パターンです。
https://github.com/microsoft/debugpy
を参照してください。

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
注意)
この話は、該当ソースコードが、ホスト側に置いてあって
それをdockerコンテナでは、バインドマウントしている状況であることが前提ですよ

そこらへんのdocker関係の知識の説明は当記事では、割愛させてもらいます
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

★★★★★★★★
★今回の諸事情★
★★★★★★★★
★★★★★★★★★★★★★★★★★★★★★★★★★★
今回は、ターゲットがpython 2.7.5であった都合上、
互換性を意識し対応がとれたバージョンを選択したため「 debugpy 1.5.1 」という
古めのバージョンにせざるを得ない状況だった

補足)
「なぜ debugpy 1.5.1?」
debugpy は 1.6.0 以降で Python 2.7 非対応。2.7 なら 1.5.1
とのことだからである
https://github.com/microsoft/debugpy/issues/884
のGitHub issueを参照してください。

★★★★★★★★★★★★★★★★★★★★★★★★★★

★★★★★★★★★★★★★★★★★★★★★★
★debugpy自体の考え方は使えるノウハウだ!★
★★★★★★★★★★★★★★★★★★★★★★
★★★★★★★★★★★★★★★★★★★★★★★★★★
debugpyのやり方、考え方、自体はpythonの新しめのバージョンでも
それに応じた新しめのdebugpyのバージョンを
dockerコンテナにインストールしたうえで、同じようなことをすることができるとのこと

★ 最新版のdebugpyでも、ほぼ同じです ★
★ 気にするべきは、pythonのバージョンにあったdebugpyのバージョンを使うこと ★
★ これを意識し、あったバージョンのdebugpyをdockerコンテナ側にインストール ★
★ 少なくとも、この記事に書いた範囲内の基本的な使い方であれば ★
★ 現時点(2025/09時点)の最新版のdebugpyでも同じ使い方ができる ★

ですので、今回の経験や、ノウハウは今後も、役立つはずだと思いまして
当記事として、まとめている次第であります。
★★★★★★★★★★★★★★★★★★★★★★★★★★

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

では、今回の話に特化した記述を進めます

dockerコンテナ側が、python 2.7.5のとき
Remote WSLでWSL2のubuntu(LTSの最新版)に接続したVSコードを
ホストとして、リモートデバッグするには
ms-python.python-2021.9.1246542782
で、バージョン固定してる状況は必須です

補足)
ms-python.python-2021.9.1246542782
より新しいバージョンでは、Python 2.7.x のサポートを事実上終了している
debugpy 1.6は、Python 2.7.x 非対応
debugpy 1.5.1が、Python 2.7.xとれる最後のバージョンで、
debugpy 1.5.1と相性がとれる最後のバージョンが
ms-python.python-2021.9.1246542782
である。

★★★★★★★★★★★★★★★★★★★★★★★★★★★
https://zenn.dev/tazzae999jp/articles/14073b30d62d95
でのノウハウが必要だったのは、
この件についての該当フォルダをVSコードで開いたときには、
ms-python.python-2021.9.1246542782
へのバージョン固定はするけど
それ以外の開発作業のため、別の作業フォルダをVSコードで開いたときには
拡張機能は最新版になっていて、他の開発案件での作業は、
影響を受けることなく、これまで通り行えるという
VSコードを開くフォルダによって、拡張機能および、そのバージョンが
別管理して作業を進められるノウハウを確立したうえで対応できるノウハウが
是非必要だからである!!

★ このように、私の記事では、WSL2に関する実践的な環境構築ノウハウが満載なため
★ 他の記事も興味があれば、是非、よろしく

★★★★★★★★★★★★★★★★★★★★★★★★★★★

そのうえで、dockerコンテナ側に、「 debugpy 1.5.1 」を
dockerコンテナにインストールした

dockerコンテナ側では、リモートデバッグしたいpython

python -m debugpy --listen 0.0.0.0:5678 --wait-for-client hoge.py

の形で実行する

VSコード側の
.vscode/launch.json
は、下記のとおりとした

// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Attach (debugpy, 5678)",
      "type": "python",
      "request": "attach",
      "connect": { "host": "127.0.0.1", "port": 5678 },
      "justMyCode": false,
      "subProcess": true,
      "pathMappings": [
        {
          "localRoot": "${workspaceFolder}/bin/pydir",
          "remoteRoot": "/usr/local/bin/pydir"
        }
      ]
    }
  ]
}

補足)
host は 127.0.0.1 明示するのが、
localhost が IPv6 に寄ってしまう環境であっても
タイムアウト回避できる定石とのこと

後でわかったのだが、このlaunch.jsonはattachするものだから
"subProcess": true,は効かないらしい
あっても害はないみたいなので、とりあえず、このままにしてる
Attach では無視される

Launch 方式とAttach 方式の説明
「Launch 方式:VS Code が“開いている環境”(ホスト or Dev Containers)側で Python を起動する。Attach 方式:Python は自力で起動し、VS Code が後から接続する。」

"remoteRoot": "/usr/local/bin/pydir"
は、
dockerコンテナ側のパスを書く場所で、
python -m debugpy --listen 0.0.0.0:5678 --wait-for-client hoge.py
で、実行した時、
その hoge.py がおいてるdockerコンテナ側のディレクトリを絶対パスで書く

バインドマウントしてる前提で、そのディレクトリに対応する
ホスト側のパスを
"localRoot": "${workspaceFolder}/bin/pydir",
のように書く

${workspaceFolder}は、VSコードで開いているフォルダである
${workspaceFolder}の直下に
bin フォルダ
docker-compose.yml ファイルがある

docker-compose.ymlの
volumes:
の定義で

volumes:
  - ./bin:/usr/local/bin

というバインドマウントしてる状況である。

python -m debugpy --listen 0.0.0.0:5678 --wait-for-client hoge.py
の方式は、dockerコンテナ側でListenし
ホスト側からAttachする
いわゆる、インバウンド方式なので、

docker-compose.ymlでは、
portsの定義として、

 - "5678:5678"

の定義が必要である
さもなくば、アタッチできず、リモートデバッグが機能しない

また、今回は、
プログラムを起動時に、8000ポートでサービスを起動する
foo.pyのリモートデバッグも成功させた
具体的には、
python -m debugpy --listen 0.0.0.0:5678 --wait-for-client foo.py
で、起動後に、
VSコードのデバッガーを起動し、アタッチする
その後、Windows側でChromeブラウザで

http://localhost:8000/functionA

などのURLでアクセスする

この時、VSコード側で foo.pyのソースコードの
該当のGETリクエストに応じたハンドラメソッドにブレイクポイントを置いてる
状況のときの場合、
そのブレイクポイントで、制御が止まって、
デバッガーによるステップ実行などが行えたのである

そのため、
8000のポートも、ホストからコンテナへの通信が行える必要があった

ここまでの話を総合し

docker-compose.ymlのports:の記述は

    ports:
      - "8000:8000"
      - "5678:5678"

となったのである。

以上

あと、同じホスト側のPC内で、
「コンテナ名」と「ホスト側のポート番号」の競合を防いで
複数同時起動できるようにする話は、
https://zenn.dev/tazzae999jp/articles/1a044f201c735d
の記事にまとめているので、よろしくです。

発端の環境構築での「docker compose」を参考情報として書いた

発端となった環境構築は下記のとおり

2025/09の下旬の話である。
本番環境が古めのLinuxでpython 2.7.5のシステムの保守開発のため、
ローカルにその環境を再現する環境を作った

開発環境としてdocker compose環境を構築した
本番はRed HatだったがDockerHub公開イメージがないため、
ベースになってるCentOSの対応するバージョンでのイメージを使った

dockerコンテナにpython2.7.5の古めのバージョンが入ってる環境で、
WSL2にRemote WSLで接続しているVSコードをホスト側として
リモートデバッグできる環境を構築した

この環境でのDockerfileと、docker-compose.ymlを示します

はっきり言って、こんなの自力で書く力はないです
AIに吐き出させて動作確認しながら、微調整していったものです。
環境がRed Hat 7.2で、そのディストリビューションでの公開イメージが
DockerHubにないため、ベースとなってるCentOSで、
バージョンが対応するものをベースのイメージとしてます。
設定値などで、公開できない部分は消しましたので、本当に使ってるものとは違います
あくまで、一般論の技術情報として公開記事として使える部分だけ残してますが
どのような事をしたら、どうなるかの今後の技術情報の参考値にはなると思います

Vaultという仕組みでやらなければならないと、ビルドが通りませんでした
詳しい理由まではわかりません。

Dockerfileは下記のとおり

# ベース:CentOS 7.2(本番が RHEL 7.2 相当のため Vault を使用)
FROM centos:centos7.2.1511

# --- Vault リポジトリ設定(mirrorlist を全削除し Vault 固定)-------------------
RUN set -eux; \
    rm -f /etc/yum.repos.d/*.repo; \
    cat > /etc/yum.repos.d/CentOS-Vault-7.2.1511.repo <<'EOF'
[base]
name=CentOS-7.2.1511 - Base
baseurl=http://vault.centos.org/7.2.1511/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

[updates]
name=CentOS-7.2.1511 - Updates
baseurl=http://vault.centos.org/7.2.1511/updates/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

[extras]
name=CentOS-7.2.1511 - Extras
baseurl=http://vault.centos.org/7.2.1511/extras/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
EOF

# ベース必須ツール(Vault環境で最低限)
RUN set -eux; \
    yum -y makecache; \
    yum -y install \
        tzdata \
        ca-certificates \
        openssl \
        curl \
        tar \
        gzip \
        bzip2; \
    update-ca-trust force-enable || true; \
    yum clean all

# 1) ロケール実体を導入 & 生成
RUN set -eux; \
    yum -y install glibc-common; \
    localedef -i ja_JP -f UTF-8 ja_JP.UTF-8 || true; \
    # 予備ロケール(英語)も作っておくと安心
    localedef -i en_US -f UTF-8 en_US.UTF-8 || true

# 2) システム既定ロケールを固定
RUN printf 'LANG=ja_JP.UTF-8\n' > /etc/locale.conf

# 3) Python I/O / 4) LC_CTYPE をENVで固定(全プロセスに継承)
ENV LANG=ja_JP.UTF-8 \
    LC_ALL=ja_JP.UTF-8 \
    LC_CTYPE=ja_JP.UTF-8 \
    PYTHONIOENCODING=UTF-8

# PYTHONIOENCODING=UTF-8 の件は、
# Python の標準入出力も一応UTF-8固定(将来の日本語出力に備え)

# ------------------------------------------------------------------
# Python 2.7.5 にて互換性の問題が発生しにくい
# pipのバージョンを導入しておく
# curl -fsSL https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py
# でダウンロードしたものを
# Dockerfileと同じ場所に置いておくこと
# ------------------------------------------------------------------
# pip 20.3.4(Py2.7 最終)導入
COPY get-pip.py /tmp/get-pip.py
RUN set -eux; \
    python /tmp/get-pip.py "pip==20.3.4"; \
    rm -f /tmp/get-pip.py

# ------------------------------------------------------------------
# debugpy 1.5.1 をオフラインインストール
# 
# https://pypi.org/project/debugpy/1.5.1/#files
# で、debugpy-1.5.1-py2.py3-none-any.whl をダウンロードして
# Dockerfileと同じ場所に置いておくこと
# debugpy-1.5.1-py2.py3-none-any.whl はプラットフォーム非依存なファイル
# のためWindows端末でダウンロードしたものをWSL2側において
# それがdockerコンテナ側へのインストールで使われる状況でも問題ありません
# ------------------------------------------------------------------
# debugpy 1.5.1 をオフラインインストール
COPY debugpy-1.5.1-py2.py3-none-any.whl /tmp/debugpy-1.5.1-py2.py3-none-any.whl
RUN set -eux; \
    pip install --no-index /tmp/debugpy-1.5.1-py2.py3-none-any.whl; \
    rm -f /tmp/debugpy-1.5.1-py2.py3-none-any.whl

# 任意:作業ディレクトリとエントリポイント(あなたの既存スクリプトを利用)
COPY docker/entrypoint.sh /opt/entrypoint.sh
RUN chmod +x /opt/entrypoint.sh
ENTRYPOINT ["/opt/entrypoint.sh"]
CMD ["bash"]

docker-compose.yml
は、下記のとおり
プロジェクト固有の秘匿性の高い情報箇所を削ってますので
本当の内容とは異なります。
一般論としての技術情報として有益な掲載という範疇で書ける部分を書いてますので
あしからず

version: "3.9"

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    working_dir: /usr/local/bin
    tty: true

    volumes:
      - ./bin:/usr/local/bin
      # ここは他にもバインドマウントを並べてるが秘匿性のある情報のため消しました

    # locale/I/O をUTF-8に固定(Python 2.7.5での encode(None) 事故回避)
    environment:
      - TZ=Asia/Tokyo
      - LANG=ja_JP.UTF-8
      - LC_ALL=ja_JP.UTF-8
      - LC_CTYPE=ja_JP.UTF-8
      - LC_TIME=ja_JP.UTF-8
      - PYTHONIOENCODING=UTF-8

    entrypoint: ["/opt/entrypoint.sh"]
    command: ["bash"]
    ports:
      - "8000:8000"   # 8000ポートをListenしブラウザアクセスに対応するプログラム用
      - "5678:5678"   # debugpy attach
      # - "127.0.0.1:8000:8000"
      # - "127.0.0.1:5678:5678"
      # にしたほうが、外部からホストPCを通じてコンテナに入れなくできるからよりよいです。

entrypoint.sh
の実装内容は、システム固有の秘匿性のある情報を含んでるため掲載しません

pythonのみんなが知ってる通常的なデバッグと何が違うのかのお話

通常はVSコードでlaunch.jsonの「type: "debugpy" or "python"」の記載をして
launch方式でデバッガーを起動すると思いますが、自分のPCや、ホスト側で動作させての
デバッグ実行のやり方です。

これがみんなが知ってるやり方ですが
これと何が違うのか

launch.jsonの「type: "debugpy" or "python"」の記載をして
launch方式でデバッガーを起動し、自分のホストで、デバッグ動作が可能
Python 3.x でも 2.x でも launch は可能
(ただし拡張・debugpy をその Python に合う版に揃える)

たしかに、上記でデバッグ動作できますが、あくまで、それは、
自分のホストでプログラムを動作させてのデバッグ実行

dockerコンテナ側のプロセスとして起動したい場合は
当記事のように、dockerコンテナにログインし
python -m debugpy --listen 0.0.0.0:5678 --wait-for-client hoge.py
で起動する必要がある。

補足)
Djangoの場合を補足します。
この場合で、djangoのアプリを起動する場合、起動コマンドは、下記のようになります
python -m debugpy --listen 0.0.0.0:5678 --wait-for-client manage.py runserver 0.0.0.0:8000 --noreload
--noreloadは必ずつけて自動リロードを抑制したほうがよい
「自動リロードが子プロセスを作る→ブレークが外れる/二重接続になるため」

そのdockerコンテナ側で起動させたプロセスに対して
ホストのVSコード側からattachして
★ 動作はコンテナだが、デバッガーはホスト側で動いてる
★ つまり、リモートデバッグをする方法を当記事で書いているのである。

このホスト側からのattachのやり方であれば
Python3.xでも、Python 2系でも可能である
ただし、拡張機能やdebugpyのバージョンは、Pythonのバージョンにあった
互換性のあるバージョンで揃える必要はあります。

別の方法は、
Dev Containers(旧称 Remote-Containers)の仕組みで、
完全に、VSコードがdockerコンテナ内にログインした状況とすることです。
この場合、Python 3.xならば、
launch.jsonの「type: "debugpy"」の記載をしてデバッグ実行できます
VSコードが完全にdockerコンテナ内部に入り込んでる状況でのことだから、
この場合は、ホストとコンテナ間のリモートでのデバッグではない。
コンテナの中だけでのデバッグ実行となる。
この場合は、ホストからコンテナへ5678を転送する必要性がない。
つまり、docker-compose.ymlに、5678ポートをports:に書く必要がない

補足)
WSL2でのDev Containers(旧称 Remote-Containers)の話は、以前
https://zenn.dev/tazzae999jp/articles/b60d353fd9329d
の記事を書きました

Dev Containers(旧称 Remote-Containers)
の場合のメリットについて、一点、補足すると
Dev Containersの環境ごとに、VSコードにインストールすべき
拡張機能を固定できることである。
そのため、
https://zenn.dev/tazzae999jp/articles/14073b30d62d95
でのノウハウは不要かと思います
ただし、Dev Containersは環境を作るのは、それなりに手間なため
多人数の開発者で長期間になりそうなアプリやシステムの開発をする場合で
多人数との環境同期を効率的にしたいという強いニーズがあれば、
有効なやり方ではあるとは思いますが、
細かい小規模案件について一時的に少人数でする分には、
環境構築が手間になってしまうデメリットがある思ってます。

ここまでの話を、

まとめると

launch 方式は
どこで launch されるかは VS Code がどの環境を開いているか次第
ホストを開いていればホストで、Dev Containers を開いていればコンテナで起動。

Attach 方式は、コンテナ起動でプログラム起動し、
ホスト側のVSコードのデバッガーがそのプロセスにAttachする方式(ポート公開が必要)。

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
プロセスが、ホストで動くか、コンテナで動くか
どちらなのかが、重要な場合あります
そもそも、今回のケースは、コンテナでしか動作できない
プログラムの保守開発のための環境構築をした時の話から記事にしました。
コンテナで動作させて本番に近い環境でのテストをしたい
その意図でdockerコンテナ作ってるのです

★ 動作はコンテナだが、デバッガーはホスト側で動いてる
★ つまり、リモートデバッグをする方法を当記事で書いているのである。

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

Discussion