Docker.py - python API for Docker
Once upon a time I and my friend decided to write an application that helps us doing code kata. The first problem that we faced was how to run a code provided by the user in a safe manner so our server won’t be destroyed. After giving it some thought I decided to write a prototype of an application that runs the code inside Docker container which is immediately destroyed after the code has been run. This blog post is about this prototype.
Assumptions
I need an application that gets a code from the user, executes it and gives output back. As many people before me said output from user cannot be trusted so I need to use some kind of container for user input. To do that I used Docker python API- docker.py. Using that and Flask I created Tdd-app-prototype. Under the hood, this application will work like this: user writes a code on a website, clicks submit. Then Docker creates a container based on python docker image and executes code. I take the output from the container and destroy it afterwards.
As we know what application should do, let’s jump into the code.
Code
The first problem that I have is that I don’t want to write a code provided by the user to a disk, then read it from the disk and it execute by Docker. I want to store it in memory - perfect case for StringIO. Code that does this looks as follows:
@app.route("/send_code", methods=['POST'])
def execute_code():
data = request.form['source_code']
code = io.StringIO(data)
create_container(code)
output = get_code_from_docker()
return output
Here beside specifying routes in Flask I take data from the form, cast
it to StringIO
and create a container from that code. Function that
does that is below:
def create_container(code):
cli.create_container(
image='python:3',
command=['python','-c', code.getvalue()],
name='tdd_app_prototype',
)
What is cli
here? I can use docker.py with Docker from other than my
own computer location so before I can use any of these functions I need
to specify Client
:
cli = Client(base_url='unix://var/run/docker.sock')
It tells docker.py to use my local Docker. Let’s go back to
create_container
. I tell docker.py to use official python 3 images.
Then I specify a command to run: python -c
and my code from
StringIO
. If you want to run standalone python script you can use
this:
def create_container(code):
cli.create_container(
image='python:3',
command=['python','-c', 'my_code.py'],
volumes=['/opt'],
host_config=cli.create_host_config(
binds={ os.getcwd(): {
'bind': '/opt',
'mode': 'rw',
}
}
),
name='tdd_app_prototype',
working_dir='/opt'
)
volumes
and host_config
keywords are for telling Docker to mount
volumes.
It is the same as running docker run -v "$PWD":/opt
. Finally I set up
working_dir
so I don’t need to provide a full path to my_code.py
. As
we have a container created now it is time to start it:
def get_code_from_docker():
cli.start('tdd_app_prototype')
cli.wait('tdd_app_prototype')
output = cli.logs('tdd_app_prototype')
cli.remove_container('tdd_app_prototype', force=True)
return "From docker: {}".format(output.strip())
I used here wait
so I wait for the container to stop. Then I take
output in form of lists and remove the container.
That’s all for today! If you want to see full code grab it here. Do you know other ways of using docker.py?
Special thanks to Kasia for being editor for this post. Thank you.