In this guide we will learn how to setup a fully containerized development environment for the Julia language.
We will go through the following steps:
- Install Docker
- Install DockStation
- Create a Julia container
- Install Atom
- Install and configure Juno IDE
- Connect to the Docker container
But first, what are Julia and Docker? The following two paragraphs are meant to give you some context but they are not needed in order to follow through the tutorial. If you can’t wait getting to work with Julia, you can directly skip to Install Docker!
About the programs
Julia is a high-level dynamic programming language designed to address the needs of high-performance numerical analysis and computational science, without the typical need of separate compilation to be fast, while also being effective for general-purpose programming, web use or as a specification language.
When programming, especially for scientific computations, one has to come into terms with both the complexity of the problem and the task of writing a good code. In physics, we want our code to run fast, but as we are not programmers we want also to quickly build a program to test new ideas.
Python is considered one of the best languages (in the scientific world) for its simplicity and the vast community, but it tends to be slow and it doesn’t always use all the computational resource we have at hand. Thus, core libraries for computation are implemented using “low level languages” such as C++ or Fortran, and they are then called by Python functions.
Languages like C++ and Fortran can be blazing fast, as the binaries are optimized for the machine they run on, but on the other hand it is difficult to code fluently in those languages. That’s the reason why ideas are often quickly prototyped using Python and then they are re-implemented using C++ for deployment.
That is the so called two language problem, i.e the need to write the code two times, to cut it short.
Julia aims to solve this problem thanks to its JIT (Just In Time) compiler: coding in Julia is as easy as coding in Python, and thanks to the possibility to write type annotations and generate type optimized code, it can be almost as fast as compiled languages. You write the code, you can interact with the code in the REPL (like an IPython notebook) but functions are compiled as soon as you run them, making your code orders of magnitude faster, in some circumstances.
For some benchmarks you can look at this github repository.
What is Docker?
Docker is a container technology for Linux that allows a developer to package up an application with all of the parts it needs.
Think of it as a Virtual Machine on steroids: it enables you to craft a developing environment in minutes with exactly the specs you need.
What I think is extraordinary about Docker, is that once you have built an image, you can install it on any machine running docker, and you can be sure that if you code works on your docker container, it will work anywhere else if you run it inside the same image.
This feature is extraordinary important, if you need to develop a program to analyze some data and you need to run it on a cluster: once you have submitted your code it will be run, and if there are some bugs it means that you have wasted computational power and possibly your research funds!
If you want to read more about docker, I encourage you to look at the project’s webiste.
First we need to install Docker. I will not delve into the details as the procedure is well explained here on the Docker website.
If you are using Windows or Mac, I suggest that you install Docker for Windows/Mac.
If you are using Linux, you can find here a guide on how to install Docker. Furthermore, after you have installed Docker, you need to also install Docker Compose running, for example
sudo apt-get install docker-compose
Watch Out! If you are using linux, you have to run all of the docker commands using sudo or it will not work.
This is an optional step, if you are already acquainted with Docker. DockStation is not required at all if you don’t want to use Docker with a GUI, though I think that it is a nice addition to have it.
DockStation is a free GUI for Docker which uses Docker Compose. Once you have installed it you are ready to start working with docker containers!
Create a Julia container
Ok, now that we have set up docker and docker compose, it is time to create a container for Julia: here is where the magic lies! I won’t go into the details of how to write a Dockerfile, but I’ll try to give you a flavour of it, in order to make you understands what’s going on.
Let’s start writing our Dockerfile. First create an empty file, named Dockerfile, then we need to open it and write a script which will build the Docker container.
Create the Dockerfile
In order to have a working Julia environment, we simply need to use the official Julia Docker image, like this
FROM julia:1.0.3 MAINTAINER Aurelio Amerio
Here, on line 1 we select the Julia version we want to use (1.0.3 is the long term support one, we could have chosen latest too). Line 2 is not mandatory, but it is useful to write an annotation of who is writing/maintaining the docker image.
Now we need to install a series of Linux packages. In order to be able to connect to the Docker container using Atom/Juno, we need to install and configure an ssh server. Furthermore, I have decided to install also a C++ compiler, as it may/will be useful to have it for future experiments.
#install required packages RUN apt-get update && apt-get upgrade -y && apt-get install -y \ apt-utils gcc g++ openssh-server cmake build-essential \ gdb gdbserver rsync vim locales RUN apt-get install -y bzip2 wget gnupg dirmngr apt-transport-https \ ca-certificates tmux && \ apt-get clean
Here we update the list of the packages and then we install everything we need.
Note that after we have installed all the packages,
apt-get clean is issued in order to free space on disk. We should try to delete all the unnecessary files and keep the image as small as possible, when we are building a Docker container.
Now we are ready to setup the ssh server:
#setup ssh RUN mkdir /var/run/sshd && \ echo 'root:root_pwd' |chpasswd && \ sed -ri 's/^#?PermitRootLogin\s+.*/PermitRootLogin yes/' /etc/ssh/sshd_config && \ sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config && \ mkdir /root/.ssh
You should change the root password (
root_pwd) on line 3 to something else, for safety reasons. Lines 3-4 are needed to fix permissions.
Now we remove the leftovers and expose the ports of the ssh server and gbd server (c++ debugger):
#remove leftovers RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # Expose 22 for ssh server. 7777 for gdb server. EXPOSE 22 7777
Exposing a port means that I will be able to access services which listen to that port, in particular I will be able to ssh into the container.
Watch Out! Usually, it is not needed to ssh into a container in order to get a bash shell. In this case, an ssh server is only needed because we want to connect to the container using Juno. As a rule of thumb, you shouldn’t install an ssh server into a container, if the container is not an ssh server itself.
We shall now add an user if we need to run a debugger or if we don’t want Julia to run as root (optional)
# add user for debugging RUN useradd -ms /bin/bash debugger RUN echo 'debugger:debugger_pwd' | chpasswd
And now we start the ssh server:
CMD ["/usr/sbin/sshd", "-D"]
Notice that we have used
CMD and not
RUN commands are run when we build the image (see next paragraph).
CMD statements are executed when we launch the container, which means that as soon as we “spin” the container, it will run the
CMDcommand. Note that it is possible to issue only one
CMD command and that any subsequent
CMD command will overwrite the previous ones.
Last but not least, we need to install some locales, in order for our keyboard to work. This step is only needed because we want to use an ssh shell together with
tmux (an advanced terminal, persistent through sessions).
#add support for English and Italian COPY locale.gen /etc/locale.gen RUN locale-gen
We need to create a file called
locale.gen in the same folder where the Dockerfile is placed, containing:
#to support other languages add them to this file en_US.UTF-8 UTF-8 it_IT.UTF-8 UTF-8
As you can see, you can easily add your language to this list, in order to have it installed on you docker container.
The whole dockerfile should now look like this:
FROM julia:1.0.3 MAINTAINER Aurelio Amerio ######################################################## # Essential packages for remote debugging and login in ######################################################## RUN apt-get update && apt-get upgrade -y && apt-get install -y \ apt-utils gcc g++ openssh-server cmake build-essential gdb \ gdbserver rsync vim locales RUN apt-get install -y bzip2 wget gnupg dirmngr apt-transport-https \ ca-certificates openssh-server tmux && \ apt-get clean #setup ssh RUN mkdir /var/run/sshd && \ echo 'root:root_pwd' |chpasswd && \ sed -ri 's/^#?PermitRootLogin\s+.*/PermitRootLogin yes/' \ /etc/ssh/sshd_config && \ sed -ri 's/UsePAM yes/#UsePAM yes/g' /etc/ssh/sshd_config && \ mkdir /root/.ssh #remove leftovers RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # Expose 22 for ssh server. 7777 for gdb server. EXPOSE 22 7777 # add user for debugging RUN useradd -ms /bin/bash debugger RUN echo 'debugger:debugger_pwd' | chpasswd ######################################################## # Add custom packages and development environment here ######################################################## ######################################################## CMD ["/usr/sbin/sshd", "-D"] #add support for English and Italian COPY locale.gen /etc/locale.gen RUN locale-gen
Now that we have written our Dockerfile, it is time to build a Docker image.
Open a terminal in the same directory where the Dockerfile and locale.gen are located (power shell if you are using Windows) and type:
docker build -t julia-container .
Note: run this command with
sudo, if you are a Linux user.
Julia contaier may be any name of your choice: it is the name (tag) of the image you are going to build. Take note of it as we will need it later.
This process may take some time, depending on your connection speed. On my pc it takes 20~30 minutes, so this might be the right time to grab a cup of tea!
Did you get your cup of tea? Nice! Now, while we wait for the build to finish, we shall head to the next step and install Atom!
If you don’t want to build the image yourself, I have built it already and uploaded it on docker hub. You can safely skip the building step and download the docker image directly, but you won’t get to change the default root/debugger password. If you plan to use Julia locally on your pc, not changing the default password won’t be a problem.
Atom is a hackable text editor for the 21st Century!
It is basically a text editor with support for syntax highlighting and open source plugins. It is made by Github (for reference, this website is hosted at Github Pages) and it offers great flexibility: there is a set of plugins to create an IDE for almost any programming languages!
You can download Atom from here. There is not much to say about it, as there is not even anything to configure while installing it, just go through the installer!
Install and configure Juno IDE
To install Juno, open a command prompt and type the following command:
apm install uber-juno
Otherwise, you can install it graphically from the
install option in the
settings menu in Atom.
Now we have to install the package
ftp-remote-edit by typing the following command into a command prompt:
apm install ftp-remote-edit
We shall now proceed to setup
First open the
settings menu in Atom and click on
packages. Now search
Scroll to the section called
Remote Options and set the path to the Julia binary (see figure)
Use a persistent tmux session,
Use ssh agent and
Forward SSH Agent.
ctrl+space in order to open the
ftp-remote-edit menu. You will be asked to set a password. You should remember that password as it will be used to secure the credentials of you ssh logins.
Right click on the new tab in order to open the context menu:
You should be able to create a new server. Compile the module with the following settings:
- IP: 127.0.0.1
- Port: 7776 (can be changed)
- Username: debugger (or root)
- Password: the password for the debugger user (
debugger_pwdif you have built the image following the tutorial or you are using my remote image)
- Initial Directory: /opt/project
Now it is time to connect Juno to the docker image!
Connect to the Docker container
If you have reached this point and the build of your Docker image is finished (or you have chosen to use my remote image), you are almost ready to go!
Now you can either create a docker compose project visually with DockStation, if you have installed it, or skip this step and use the configuration file which is provided at the end of this paragraph.
First we need to start DockStation. If you are using DockStation version >1.5, you will be prompted to create an account (for free) or use it as guest. It is your choice, it will work fine in guest mode too.
Add a new project:
… and set the path to your project directory.
Watch Out! You can either choose the path to where your code is stored or another folder. It is a good idea to set the path to the root of your Julia project. For example, a good working directory may be a new folder called
julia-projects in your documents folder!
Once you have created the project you need to add some containers to it. You can either add local or remote containers. A local container is the one you have built before with the
docker build command, a remote container is a container which is hosted on docker hub. We will now use the container which is hosted on my docker repository for simplicity’s sake. You can even create a docker repository yourself for free and link it to your Github account, so that you can build docker images completely online, but this is beyond the scope of this tutorial. If you want to learn how to build an image automatically from a Dockerfile hosted at Github, please refer to this guide!
Type in the search box “aureamerio” and look for “techytok-examples”
Drag and drop the image into the region on the right. Select
julia-docker as the tag.
Watch Out! Currently DockStation doesn’t select the tag properly, so we have to make sure that the tag is correctly chosen manually.
Click on the
EDITOR tab (top right) and edit the config file to make it look like this:
version: '3' services: julia-docker: image: aureamerio/techytok-examples:julia-docker
… and hit save.
Click now on the
GENERAL tab and press
START on the top left (see image)
Once it has started and it has downloaded the image, you can stop the container. Now DockStation has detected the settings of the container and you can fiddle with the volumes and ports.
We need to mount the directory where we intend to store our code inside the Docker container. To do so, we use the
Go to the
Volume tab, click
ADD FIELD and type
/opt/project as the docker directory and
./ as local directory. The Docker container will now see the new folder
/opt/project, which is linked to the local directory. Everything which is contained in the aforementioned folder will be modifiable by both the Docker container and your local machine and the content will be synced.
Write the path to your project directory instead of
./ , if your DockStation project directory doesn’t coincide with the directory containing your Julia code.
Now head to the
PORTS tab, click on the two icons in figure:
and set the local ports as following:
- 22 goes to 7776 (or the port you have set in ftp-remote-edits)
- 7777 goes to 7777 (or whichever port you desire the c++ debugger points to, for this example it doesn’t matter)
Now you are ready to spin your docker container! Press
START and… done! Let’s get back to Atom!
Watch Out! If asked, you should give Docker permission to share the required drive.
For Windows users: If you have kaspersky, or another antivirus with a firewall, you may have to remove filtering on tcp port 445 locally. See this post for more informations.
Docker compose file
If you did everything right, you should now have a file called
docker-compose.yml in your project folder with the following content:
version: '3' services: julia-docker: image: aureamerio/techytok-examples:julia-docker ports: - '7776:22' - '7777:7777' volumes: - './:/opt/project'
If you don’t want to use DockStation, you can start the container by:
- opening a terminal (power shell)
- changing directory to the one which contains the
- typing the command
Connect to Docker
Now that docker is running, we shall go back to Atom, open the
ftp-remote-edit tab (
ctrl+space) and click on julia-docker.
If you did everything correctly, now you should see the content of your current project folder.
Now you can click on the Planet-like icon on the left-side palette in order to start a remote kernel (your docker kernel!)
You can test your installation by creating a simple hello Julia script called
Julia will download some initial packages and compile them. After that, you should see the Julia logo and a warm welcome!
You have successfully set up Julia to work with a Docker container. Now, when you want to start coding with Julia, you simply need start you containers either via DockStation or
docker-compose up and connect to them using Atom and the Juno IDE.
Have fun exploring Julia! I hope you enjoyed this tutorial, if you liked it stay tuned for further guides here, at TechyTok!