To debug app in docker (or remote server) you will need to run debugpy localy and on target machine. You need to:
- Install debugpy localy
- Install debugpy on remote machine
- Expose ports for connection
- Attach on remote machine debugpy to working app (or start with debugpy attached)
- Attach local debugpy to this working on remote machine
- Be sure that it runs on Python version that does not have any problems with this (Python 3.9.4 should work)
Import plugins
call plug#begin('~/.vim/plugged')
" ...
Plug 'mfussenegger/nvim-dap'
Plug 'rcarriga/nvim-dap-ui'
" https://github.com/microsoft/debugpy
Plug 'mfussenegger/nvim-dap-python'
" ...
call plug#end()
*dap*, *dapui* setup with support of *dap-python*
lua << EOF
require('dap-python').setup(python3_host_prog)
require('dap-python').test_runner = 'pytest'
require("dapui").setup({
icons = { expanded = "▾", collapsed = "▸" },
mappings = {
expand = { "<CR>", "<2-LeftMouse>" },
open = "o",
remove = "d",
edit = "e",
repl = "r",
toggle = "t",
},
expand_lines = vim.fn.has("nvim-0.7"),
layouts = {
{
elements = {
{ id = "scopes", size = 0.25 },
"breakpoints",
"stacks",
"watches",
},
size = 50, -- 40 columns
position = "right",
},
{
elements = {
"repl",
"console",
},
size = 0.25, -- 25% of total lines
position = "bottom",
},
},
floating = {
max_height = nil,
max_width = nil,
border = "single",
mappings = {
close = { "q", "<Esc>" },
},
},
windows = { indent = 1 },
render = {
max_type_length = nil,
}
}
)
EOF
My mappings (mostly copy/paste from others setup)
lua << EOF
vim.fn.sign_define('DapBreakpoint', {text='🔴', texthl='', linehl='', numhl=''})
vim.fn.sign_define('DapBreakpointRejected', {text='🟥', texthl='', linehl='', numhl=''})
vim.fn.sign_define('DapStopped', {text='🟢', texthl='', linehl='', numhl=''})
vim.keymap.set('n', '<leader>dh', function() require"dap".toggle_breakpoint() end)
vim.keymap.set('n', '<leader>dH', ":lua require'dap'.set_breakpoint(vim.fn.input('Breakpoint condition: '))<CR>")
vim.keymap.set('n', '<C-k>', function() require"dap".step_out() end)
vim.keymap.set('n', "<C-l>", function() require"dap".step_into() end)
vim.keymap.set('n', '<C-j>', function() require"dap".step_over() end)
vim.keymap.set('n', '<C-h>', function() require"dap".continue() end)
vim.keymap.set('n', '<leader>dn', function() require"dap".run_to_cursor() end)
vim.keymap.set('n', '<leader>dc', function() require"dap".terminate() end)
vim.keymap.set('n', '<leader>dR', function() require"dap".clear_breakpoints() end)
vim.keymap.set('n', '<leader>de', function() require"dap".set_exception_breakpoints({"all"}) end)
vim.keymap.set('n', '<leader>da', function() require"debugHelper".attach() end)
vim.keymap.set('n', '<leader>dA', function() require"debugHelper".attachToRemote() end)
vim.keymap.set('n', '<leader>di', function() require"dap.ui.widgets".hover() end)
vim.keymap.set('n', '<leader>d?', function() local widgets=require"dap.ui.widgets";widgets.centered_float(widgets.scopes) end)
vim.keymap.set('n', '<leader>dk', ':lua require"dap".up()<CR>zz')
vim.keymap.set('n', '<leader>dj', ':lua require"dap".down()<CR>zz')
vim.keymap.set('n', '<leader>dr', ':lua require"dap".repl.toggle({}, "vsplit")<CR><C-w>l')
EOF
Run *dapui* on debuging start
lua << EOF
local dap, dapui = require("dap"), require("dapui")
dap.listeners.after.event_initialized["dapui_config"] = function()
dapui.open()
end
dap.listeners.before.event_terminated["dapui_config"] = function()
dapui.close()
end
dap.listeners.before.event_exited["dapui_config"] = function()
dapui.close()
end
EOF
nvim-dap supports launch.json
file that typically exists in .vscode/
directory.
I couldn't find any explicite example but it look like it can be used in root directory as dap-launch.json
(and it works).
It should be set as remote
and attach
.
In theory in pathMappings
you should be able to map your local path with remote one.
{
"pathMappings": [
{
"localRoot": "/local/path/to/project",
"remoteRoot": "/remote/path/to/project",
}
]
}
If this work for you then great, but if not here is hacky solution:
I had a lot of trouble with that (even when i mapped and everything was working correctly my breakpoints where always pointing to file with my local path but on remote machine). So as a result I decided to configure my testing docker such my local path is mapped with target remote directory (so this one that I will use in my production Dockerfile) but also with exactly same path in remote (so both remote paths should point to the same files).
services:
app-name:
volumes:
- /local/path/to/project:/code/app
- /local/path/to/project:/local/path/to/project # This is not an error, both paths should be same
This way even if my local debugpy say that the breakpoint is in the file in path from my local machine it is correctly undrestand by remote machine.
python3 append_docker_file.py app_name_in_docker_compose
For my own usage I created script that takes your existing docker-compose.yml
and filled with necessary changes.
It requires you to start this code from root of your project. You should also have docker-compose.yml
and run-test.sh
which can look like:
python -m pip install debugpy #install debugpy
# start debugpy save logs to file listen on 0.0.0.0 (important) start your server (can be uvicorn) with only one worker
python -m debugpy --log-to "/temp/logs/" --listen 0.0.0.0:5678 -m uvicorn app-name.app:app --reload --port 8000 --host 0.0.0.0 --workers 1
It creates docker-compose.testing.yml
and dap-launch.json
.
To start debug session you can run:
docker-compose -f ./docker-compose.yml -f ./docker-compose.testing.yml up --build -d
It takes your existing docker-compose.yml
and overwrites volumes (mostly because now in docker-compose you cant extend list of values)
In Nvim try to "attach to the remote" (require"debugHelper".attachToRemote()
) and you should be able to connect with 0.0.0.0
and port 5678
.
ssh -L localhost:${your_debug_port}:${remote_docker_host}:${your_debug_port}