Containerized Development Environment for Flask + Angular + MySQL
Should you use containers and how
If you look for this topic on the Internet you will see that (as always) there are lovers and detractors about containerize your environment. Obviously it has its benefits and its drawbacks, it is you and your team who has to decide what weights more.
In this article I will to talk about both the benefits and drawbacks of using a containerized environment, and then I will set up a complete environment with Flask as backend, Angular for the frontend and MySQL as database.
Why use it
How many times have you ever come to work on a project where you have had to install so many things before write a single line of code? Or how many times have you had dependency problems? your team mate developed something and when you try it it does not work and you realized that you were working with different versions.
To me, these two were the big selling points. This is why I started thinking about containerized my environment in the beginning. To help newcomers working in the project, as they will just need to have installed Docker and docker-compose (you will see in the example) and to prevent dependency problems, as everyone will work in the same environment which is under version control system. But obviously, that’s not all.
Unless you have a project under .asp or .net technology, you will probably deploy in a Linux server. If you containerized your environment you will have a much more consistent environment. You can have developers developing in almost the same developing and production environment despite the OS they use eliminating cross-platform compatibility issues almost completely. What’s more, with a containerized environment you can reproduce production behaviors and escalate the services starting more containers and see how they behave.
On of the things that from my point of view is really important to is that when you containerize your environment you will face integration problems since the beginning, in an early stage, with plenty of time an a lot of room for changes.
Why not use it
I have to be honest, I have no much experience in real live with this. Just what I have learnt by myself in my free time, and a small project at work where I started to create a development environment just like the one I am about to explain in the following section.
However I have look for this topic on the Internet and after reading a few articles and discussions I mainly agree in two points. In one hand, it increases the complexity as adds a new layer. The developers have to adapt to a new way of working as everything must be run inside the container. In the other hand it can reduce performance, I read one discussion that pointed out that if you use Docker from Mac you could experience a significantly worse performance than a native environment when working with mounted volumes. You can see the Docker documentation talking about it.
Another area that it’s tricky is security but I will not discuss in depth any of this as this is a topic that I know very little and I prefer to shut down than look like a fool. I will just comment three things: First, you will see that I use Alpine Linux, which is a security-oriented, lightweight Linux distribution; second, I create the image with a root user bu then I use a user without root privileges that is the one that runs the containers; and third, I have no third point, but when talking about security in containers this videos always comes to my mind:
Example of containerized environment
Now is play time. As I mentioned at the beginning I will configure a stack with Flask, Angular and MySQL. It consists in a backend with a simple CRUD for dishes that store them in a MySQL database and a simple frontend that just displays the stored dishes, both services have even a few tests.
The backend uses the SQLAlchemy ORM to connect with the MySQL database, I will not talk about it as the article will be too long, you can see the code to see how is configured. If you have any question you can ask :)
You can find the complete code in my repository.
I this section I will show you the Dockerfile of each service (but MySQL as it is just a public image), then the docker-compose file that orchestrates all the services and its communications, a Makefile that simplifies the interactions with the running containers and how to debug the code running inside the container with Visual Code.
Let’s begin with it!
1. Dockerfiles
As I mentioned before, each service will be in it’s own container. The database will be created from a public images, but the backend and frontend will be created from an image that I will declare.
Backend:
Frontend:
If you look closely you will see that in both cases I am using alpine images.
A minimal Docker images based on Alpine Linux with a complete package index and only 5 MB in size!
What is Alpine Linux?
Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox. https://alpinelinux.org/
Cool isn’t it. At the beginning I used Ubuntu images where I installed everything I needed and each service container could weight around 1,5Gb and now:
Probably someone with more experience would shrink it even more. I have seen containers much smaller but not with the ability to listen to changes you made in your host machine, in these cases when you make a change you must rebuild the image, with my containers it will just reload the code.
2. Docker-compose
I use docker-compose to orchestrate all the containers integrated in my environment.
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration.
Here is it:
Now that I have the Dockerfiles and the docker-compose, I can run from the development_environment directory:
docker-compose up
The first time, this command will build the images for each service and start them.
If you make a change in the Dockerfile you must rebuild the image. You have two possibilities:
docker-compose up --build
docker-compose build and then docker-compose
Once is finished, you can check that everything is working by visiting:
http://localhost:5000/ for the backend:
and http://localhost:4200/ for the frontend:
As I said before, for this article I have created a dummy CRUD for dishes in the backend and a simple frontend where display the dishes stored in the database, it is not prepared to create dishes from the frontend as it is not the scope of this article.
You can create dishes by making a POST request to localhost:5000/api/dishes. You can see and example in backend/server/controllers
If you click in the frontend menu, you can visit the Dishes page and see the stored dishes:
3. Makefile
As I said before, one of the drawbacks of working in a containerized environment is that everything have to be run inside the container.
To do so, you have 2 possibilities, you can get into the container (and then run the command):
docker exec -it CONTAINER_ID sh
or you can run the command inside the container from out site:
docker exec -t CONTAINER_ID command to run
As this is a little tedious, first you have to get the CONTAINER_ID and then run the command, that’s why I always like to create a Makefile where define some easy commands that Make translates them to the complicated ones:
This is an example of the Makefile, if you want to see the complete Makefile, just go to my repo:
With this, if a developer want to get into the backend to execute something, just runs:
make backend-access
4. Debug
As soon as you start having any problem during development, it’s a must to be able to debug your code. This was a tricky part, because you want to put some breakpoints in the code hosted in your machine, but you want to debug a code that is running inside the container (remote).
In this article I will explain how to configure Visual Studio Code for debug in this situation. I am going to show you the launch.json file that is used to configure the debugger.
Backend:
This is the launch.json for the backend:
Here the magic is in the pathMappings, as we can see, is mapping the local files in the host with the remote code in the container. It is important to mind from where you open the Visual Studio Code.
In this case I opened from the root of the project, if you like to open each service code in a different Visual Studio Code, you must change the localRoot to “${workspaceFolder}/server/”.
For the backend I will need to start the server in a different way, adding the ptvsd (the module use for debug) and sharing other ports. In the repo, there is another docker-compose file that configures all its needed for this situation.
As you can see it shares some ports, execute a command that uses the ptvsd module and connects to the network were is already running the database.
Beware that you must have in your host machine the same ptvsd version than the one that is running in the container, this is the only dependency that you will have in your machine. You can find out the version in the backend/Pipfile.log.
Once again, there is a command in the Makefile that simplifies this action. You can just run:
make backend-debug
When you want to start the services declared in a docker-compose file but with a diferent name, you have to run docker-compose -f filename up.
Let’s see it working. As you can see, there is a home endpoint that just returns ‘The server is running!!’ I can put some dummy code to see the debugger working.
The steps are:
- Start the debugger with the command make backend-debug
- Start the debugger in Visual Studio Code with the Backend debug configuration
- Visit localhost:5001/
This will make the code stop at the break point and make us really happy.
Sorry we have to stop, is time to dance:
Frontend:
For the frontend, first I need to install an extension in Visual Studio Code:
In this case I need to configure the launch.json file too to declare a new configuration. As there is already a configuration for the backend, I just added the new one:
Then, I just need to start the Frontend debug configuration and a new window will open. Let’s see it working!
I left a button in the homepage of the frontend that says “Debug me!”, so let’s listen to the poor button. I can put a break point in its function (home.component.ts), start the debugging and hit the button.
The debugger stopped at the break point :)
That’s it, we are ready to go, but before we keep going, we need to dance again:
We have talked about
We have come a long way. In this article I have talk about:
- the benefits and the drawbacks of a containerized development environment
- the necessary Dockerfiles to containerize the services (Flask backend and Angular frontend) which were build from alpine images
- the docker-file that help us to manage and configure the containers
- the Makefile and how can help us to interact with the containerized services
- how to debug the code running in the containers
Conclusions
Now that you have reach the end of the article, you should ask again, should you containerize our development environment?
You must analyze the situation with the rest of the team to see what weights more. As we have seen through the article, in one hand, the containerized environment give you a consistent environment between developers and between development and production, reducing dependency problems. In the other hand, is true that it might complicate a little the workflow and in some cases it can reduce the performance (as everything is run inside the container).
For my little experience, in the projects I have work in, I thing it’s worth it!
That’s it, if you have reach the end of the article, and read everything, thank you, it was a long one:
Annotations
- If you want to see the code you can visit my repo.
- All the make commands must bu run form development_environmet/
- If you see that you are not able to set the break point, or as soon as you start the debugger, the break point change:
This is probably because you have set the wrong webRoot or pathMapping.