How to connect PyCharm to a python interpreter located inside a Docker container?
UPDATE: PyCharm 2017.1 has a solution for this problem, see this blog entry
Here is how I solved the problem. My circumstances are that I was assigned to do an intervention on a specific area of a web app that used docker-compose to create a set of four containers. Docker-compose is a kind of meta docker that manages multiple docker containers from one command. I did not want to mangle their existing setup since so many things depend on it. But since I was working on one specific part in one of the images I decided that I would extend one of the containers with ssh so that I could debug from PyCharm. Further, I wanted the app to run as normal when started and only by forcing it to quit and then connecting to it from PyCharm would I have a debuggable component. Here is what I did on my mac that uses boot2docker (on VirtualBox) to setup docker correctly.
First, I need to extend the target container, called
jqworker. I am going to use
supervisior to do the heavy lifting of managing things.
FROM jqworker # Get supervisor to control multiple processes, sshd to allow connections. # And supervisor-stdout allows us to send the output to the main docker output. RUN apt-get update && apt-get install -y supervisor openssh-server python-pip && pip install supervisor-stdout && mkdir -p /var/run/sshd && mkdir -p /var/log/supervisor && mkdir -p /etc/supervisor/conf.d COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf # Fix up SSH, probably should rip this out in real deploy situations. RUN echo root:soup4nuts | chpasswd RUN sed -i s/PermitRootLogin without-password/PermitRootLogin yes/ /etc/ssh/sshd_config # SSH login fix. Otherwise user is kicked off after login RUN sed [email protected]*requireds*[email protected] optional [email protected] -i /etc/pam.d/sshd ENV NOTVISIBLE in users profile RUN echo export VISIBLE=now >> /etc/profile # Expose SSH on 22, but this gets mapped to some other address. EXPOSE 22 # Replace old entrypoint with supervisiord, starts both sshd and worker.py ENTRYPOINT [/usr/bin/supervisord]
Supervisor lets me run multiple tasks from one command, in this case the original command and SSHD. Yes, everyone says that SSHD in docker is evil and containers should this and that and blah blah, but programming is about solving problems, not conforming to arbitrary dicta that ignore context. We need SSH to debug code and are not deploying this to the field, which is one reason we are extending the existing container instead of adding this in to the deployment structure. I am running it locally so that I can debug the code in context.
Here is the
supervisord.conf file, note that I am using the
supervisor-stdout package to direct output to supervisor instead of logging the data as I prefer to see it all in one place:
[supervisord] nodaemon=true [program:sshd] command=/usr/sbin/sshd -D [program:worker] command=python /opt/applications/myproject/worker.py -A args directory=/opt/applications/myproject stdout_events_enabled=true stderr_events_enabled=true [eventlistener:stdout] command = supervisor_stdout buffer_size = 100 events = PROCESS_LOG result_handler = supervisor_stdout:event_handler
I have a build directory containing the above two files, and from a terminal in there I build the
docker build -t fgkrqworker .
This adds it so that I can call it from
docker-compose. Dont skip the trailing dot!
Since the app uses
docker-compose to run a set of containers, the existing
WORKER container will be replaced with one that solves my problems. But first I want to show that in another part of my
docker-compose.yml I define a mapping from the containers to my local hard drive, this is one of a number of volumes being mapped:
volumes: &VOLUMES ? /Users/me/source/myproject:/opt/applications/myproject
Then the actual definition for my container, which references the above
jqworker: &WORKER image: fgkrqworker privileged: true stdin_open: true detach: true tty: true volumes: <<: *VOLUMES ports: - 7722:22
This maps the SSH port to a known port that is available in the VM, recall I am using
boot2docker which rides on VirtualBox, but the needs to be mapped out to where PyCharm can get at it. In VirtualBox, open the
boot2docker VM and choose
Adapter 1. Sometimes the Attached to: combo unselects itself, so watch for that. In my case it should have
Click Port Forwarding and map the inner port to the a port on localhost, I choose to use the same port number. It should be something like:
- Host IP:
- Host Port:
- Guest IP:;
- Guest Port:
Note: be careful not to change the boot2docker
ssh setting or you will eventually be unable to start the VM correctly.
So, at this point we have a container that extends my target container. It runs ssh on port
22 and maps it to
7722 since other containers might want to use
22, and is visible in the VirtualBox environment. VirtualBox maps
7722 to the localhost and you can ssh into the container with:
ssh [email protected] -p 7722
Which will then prompt for the password, soup4nuts and you should be able to locate something specific to your container to verify that it is the right one and that everything works OK. I would not mess with root if I were deploying this anywhere but my local machine, so be warned. This is only for debugging locally and you should think twice or thrice about doing this on a live site.
At this point you can probably figure the rest of it out if you have used PyCharms remote debugging. But here is how I set it up:
First, recall that I have
docker-compose.yml mapping the project directory:
In my container
/opt/applications/myproject is actually
/Users/me/source/myproject on my local hard drive. So, this is the root of my project. My PyCharm sees this directory as the project root and I want PyCharm to write the
.pycharm_helpers here so that it persists between sessions. I am managing source code on the mac side of things, but PyCharm thinks it is a unixy box elsewhere. Yes, it is a bit of kludge until JetBrains incorporates a Docker solution.
First, go to the Project X/Project Structure and create a Content Root of the local mapping, in my case that means
Later, come back and add
.pycharm_helpers to the excluded set, we dont want this to end up in source control or confuse PyCharm.
Go to the Build, Execution, Deployment tab, pick Deployment and create a new Deployment of SFTP type. The host is localhost, the port
7722, the root path is
/opt/applications/myproject and the username is
root and password is
soup4nuts and I checked the option to save the password. I named my Deployment dockercompose so that I would be able to pick it out later.
On the Deployment Mappings tab I set the local path to
/Users/me/source/myproject and deployment and web path to a single / but since my code doesnt correspond to a URL and I dont use this to debug, it is a placeholder in the Web Path setting. I dont know how you might set yours.
On the Project X/Project Interpreter tab, create a new Remote Python Interpreter. You can pick the Deployment Configuration and choose the
dockercompose configuration we created above. The host URL should fill in as
ssh://[email protected]:7722 and the Python Interpreter Path will likely be
/usr/bin/python. We need to set the PyCharm Helpers Path as the default will not survive the container being redone. I actually went to my project local directory and created a
.pycharm_helpers directory in the root, then set the path here as
/opt/applications/myproject/.pycharm_helpers and when I hit the OK button it copied the files up to the directory. I dont know if it will create it automatically or not.
Dont forget that the
.pycharm_helpers directory should probably be excluded on the project roots tab.
At this point you can go to the Build, Execution, Deployment tab, and under Console/Python Console, pick the remote interpreter we created above and set the working directory to
/opt/applications/myproject and you can run your Python Console in the container if you like.
Now you need to create a Run Configuration so that you can remotely debug your python code. Make a new Python configuration and set the script to the one that used to start the python code in the container. Mine, from the supervisor setup, above is:
/opt/applications/myproject/worker.py -A args
So I set the script to
/opt/applications/myproject/worker.py and the parameters to
Choose the remote interpreter we created above, and the working directory as needed, for me it is
/opt/applications/myproject and for me that does the job.
Now I want to enter my container and stop the
worker.py script so I can start up a debug version. Of course, if you like you can ignore running the script by default and only use the container for debugging.
I could open a ssh session to stop the script, but docker provides a useful command that will do the work for me by passing it into the environment.
$> docker exec -i -t supervisorctl stop worker
As my process is named worker. Note that you can restart by replacing the
stop command with
Now, in PyCharm start a debug session with the Run Configuration created above. It should connect and start things up and give you console output in the window. Since we killed the one that Supervision originally started it is no longer connected.
This was a seat of the pants operation, so there may be errors and incorrect assumptions I didnt notice. Particularly, the PyCharm setup required a few iterations, so the order may be incorrect, try going through it again if it fails. This is a lot of stuff and easy to skip something critical.
In order to avoid any SSH overhead (which makes perfect sense with Docker),
docker exec definitely seems to be the way to go.
Unfortunately I couldnt get it to work so far. It would be great if someone could fill in the blanks. Here is what I did (using PyCharm 4.0.4 and Docker 1.4.1):
Create a file named
python_myproject.shcontaining the following:
#!/bin/bash docker exec -i myproject_container /path/to/containers/python2.7
Note that the files name has to begin with
pythonotherwise PyCharm will complain.
In PyCharms settings, under
Project Interpreter, add a new local interpreter. Give it the path to your
This is where Im stuck. After a quite long loading time (the throbber says Setting up library files), a window entitled Invalid Python SDK appears and says:
Cannot set up a python SDK
The SDK seems invalid.
2015-02-19 17:33:30,569 [ 166966] WARN - ution.process.OSProcessHandler - Cannot kill process tree. Trying to destroy process using Java API. Cmdline: 2015-02-19 17:34:30,628 [ 227025] WARN - ution.process.OSProcessHandler - Cannot kill process tree. Trying to destroy process using Java API. Cmdline: 2015-02-19 17:34:30,653 [ 227050] INFO - rains.python.sdk.PythonSdkType - Timed out
How to connect PyCharm to a python interpreter located inside a Docker container?
Its not yet here, but shortly this should no longer be a problem, since
Docker support will be introduced in PyCharm starting with PyCharm 4.1 EAP (beginning of April)