Today, i waked up with a great idea: Learn how to build a docker-compose from scratch.
Actually, on my linux machine, i don't have any development environment prepared to run something, because i've thinking about a big goal, that is run everything on docker. Yeap, i don't even wanna install any package manager. I don't have experience working with python, but i wanna use it to build my first environment.
If you have no idea what docker is, i recommend watch this video and this one that will give an overview of how docker works.
For installing the Docker follow the instructions and this one to install docker-compose.
What's docker compose?
In short, docker-compose works as a manager of docker containers. Let's supose that you are creating a web app where you are going to split it in 3 differents containers (front-end, api, database). This way, you may create a dockerfile for each environment individually.
What's our challenge?
We're going to create a python api using flask. I'm not used to develop in python, i've studied some features and you can checkout this on my github.
Hands-on
In this section i'm going to explain the structure, and our python application associated files.
Let's create our directory structure:
- api-nasa/
- requirements.txt
- app.py
- Dockerfile
- docker-compose.yml
The file api-nasa/requirements.txt represents the list of external libraries that is going to be used, in our case we only need the flask library:
flask
I don't want to dig in details about our api-nasa/app.py implementation for now, for that, i'm just creating a basic endpoint and you can see some details in the code's comments:
import os
from flask import Flask, jsonify
app = Flask(__name__)
#we define the route /
@app.route('/')
def welcome():
# return a json
return jsonify({'status': 'api working'})
if __name__ == '__main__':
#define the localhost ip and the port that is going to be used
# in some future article, we are going to use an env variable instead a hardcoded port
app.run(host='0.0.0.0', port=os.getenv('PORT'))
Dockerhub
It's an image's repository, where you can register your own image. Let's say that you want to Download a image from Node JS, you only need to run the following command: docker pull node. After that,to check if image is there, just run docker images.
Dockerfile
Let's see the Dockerfile as a recipe, where from that, it's going to be built the os image.
FROM python:3.6
RUN mkdir /usr/src/app/
COPY . /usr/src/app/
WORKDIR /usr/src/app/
EXPOSE 5000
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
Let's check each line:
FROM python:3.6
We set the image that we wanna use, in our case lets
use the python in version 3.6. If the image is not downloaded in your computer (open the terminal and type docker images), it's going to search and download on DockerHub. This part is really interesting, because this dockerfile, won't generate a container, but an image, and you can upload to docker hub to use in the future.
RUN mkdir /usr/src/app/
Run is used to run operations inside the container, for sample, if you want to install any additional program, you just use the command RUN apt-get install whatever -y. In this case, we're creating the folder where our application will run in the container.
COPY . /usr/src/app/
It's going to copy everything from ./ local directory and move to /usr/src/app/ host directory.
WORKDIR /usr/src/app/
It sets the path where the CMD and the RUN commands will run.
EXPOSE 5000
Exposes the port 5000 of the container to be external accessed.
RUN pip install -r requirements.txt
Executes when the image is been generated. It works for when you want to install something in the image.
CMD ["python", "app.py"]
It sets the command that's going to run when the container starts. We've defined the startup of our application. Maybe you can ask: Why not put this command in the previous RUN? It's because this command only runs when container start, let's say that it's the entrypoint of container.
docker-compose
Here where the magic start to happens. We're going to dig in details about implementation. How you can see in the file, we have identation because we're using YAML files, so, if the instruction have some properties then the bottom line is more indented.
version: '3.8'
services:
api-service:
build: ./api-nasa/
volumes:
- ./api-nasa/:/usr/src/app/
ports:
- 5000:5000
environment:
PORT: 5000
FLASK_DEBUG: 1
Let's check each line:
version: '3'
here you define the version of docker-compose that you're using. Actually the current is the 3.
api-service:
It's our api service.
build: ./api-nasa/
In this section, we say to docker-compose the path where our Dockerfile is.
volumes:
- ./api-nasa/:/usr/src/app/
Volumes are the way that we sync the files in our local computer to our containers. We can define volumes in Dockerfiles but only if you're handling container straight, but,how the handler of our container is docker-compose, we are defining them here.
ports:
- 5000:5000
We've talked about PORTS in Dockerfile, but here we can set a port on our localhost where you can access the container. Really cool, not? Basically we are saying: "Listen the port 5000 in our container and set the port 5000 to your localhost".
environment:
PORT: 5000
FLASK_DEBUG: 1
It's really interesing, you can define environment variables and get them in the language that you're going to use. You can checkout in the python file api-nasa/app.py the code snippet os.getenv('PORT').
After everything is ready, let's run our docker-compose file.
docker-compose up
This should be the result:
To check if it's working, acess the url http://localhost:5000 in your browser.
You can checkout the project on my github. See you folks.
Top comments (0)