Docker, jedna od najnovijih ludosti, nevjerojatan je i moćan alat za pakiranje, otpremu i pokretanje aplikacija. Međutim, razumijevanje i postavljanje Dockera za vašu određenu aplikaciju može potrajati malo vremena. Budući da je internet prepun konceptualnih vodiča, neću ići previše duboko konceptualno o kontejnerima. Umjesto toga, objasnit ću što znači svaki redak koji napišem i kako to možete primijeniti na svoju specifičnu aplikaciju i konfiguraciju.

Zašto Docker?
Dio sam studentske neprofitne organizacije pod nazivom Hack4Impact na UIUC-u, gdje razvijamo tehničke projekte za neprofitne organizacije kako bismo im pomogli u daljnjem radu. Svakog semestra imamo više projektnih timova od 5–7 studentskih programera, s različitim razinama vještina, uključujući studente koji su završili tek prvi tečaj informatike na fakultetu.
Budući da su mnoge neprofitne organizacije često tražile web aplikacije, kurirao sam Flask Boilerplate kako bih timovima omogućio brzo pokretanje i pokretanje njihovih pozadinskih REST API usluga. Uobičajene korisne funkcije, struktura aplikacija, omotači baze podataka i veze pružaju se zajedno s dokumentacijom za postavljanje, najboljim praksama kodiranja i koracima za Heroku implementaciju.
Pitanja oko razvojnog okruženja i ovisnosti
Međutim, budući da smo svakog semestra ukrcavali nove studentske programere, timovi bi trošili puno vremena na konfiguriranje i rješavanje problema s okolinom. Često bismo imali više članova koji su se razvijali na različitim operativnim sustavima i naišli na bezbroj problema (Windows, pokazujem na vas). Iako su mnogi od tih problema bili trivijalni, poput pokretanja ispravne verzije baze podataka PostgreSQL s pravim korisnikom / lozinkom, bilo je izgubljeno vrijeme koje se moglo staviti u sam proizvod.
Uz to, napisao sam dokumentaciju za korisnike MacOS-a samo s bash uputama (imam Mac), a korisnike Windows i Linux u osnovi sam ostavio da se ne osuše. Mogao sam okrenuti neke virtualne strojeve i ponovno dokumentirati postavljanje za svaki OS, ali zašto bih to učinio ako postoji Docker?
Uđite u Docker
S Dockerom se cijela aplikacija može izolirati u spremnike koji se mogu prenijeti s računala na stroj. To omogućuje dosljedna okruženja i ovisnosti. Dakle, možete "jednom graditi, pokretati bilo gdje", a programeri će sada moći instalirati samo jednu stvar - Docker - i pokrenuti nekoliko naredbi za pokretanje aplikacije. Pridošlice će se moći brzo početi razvijati bez brige o svom okruženju. Neprofitne organizacije također će moći brzo unositi promjene u budućnosti.
Docker ima i mnoge druge prednosti, poput njegove prijenosne i resursno učinkovite prirode (u usporedbi s virtualnim strojevima), te kako možete bezbolno postaviti kontinuiranu integraciju i brzo implementirati svoju aplikaciju.
Kratki pregled osnovnih komponenti Dockera
Mnogo je resursa na mreži koji će Docker objasniti bolje nego što mogu, pa ih neću previše detaljno pregledavati. Evo sjajnog bloga o njegovim konceptima i još jednog o Dockeru. Međutim, pregledat ću neke temeljne komponente Dockera koje su potrebne da bi se razumio ostatak ovog bloga.
Docker slike
Slike Dockera predlošci su samo za čitanje koji opisuju Docker spremnik. Oni uključuju posebne upute napisane u Dockerfileu koja definira aplikaciju i njezine ovisnosti. Shvatite ih kao snimku svoje prijave u određeno vrijeme. Dobit ćete slike kad dobijete docker build
.
Docker kontejneri
Docker spremnici su primjerci Dockerovih slika. Uključuju operativni sustav, aplikacijski kôd, vrijeme izvođenja, sistemske alate, sistemske knjižnice itd. Možete povezati više Docker spremnika zajedno, poput posjedovanja aplikacije Node.js u jednom spremniku koji je povezan sa spremnikom baze podataka Redis. Vodit ćete Docker Container sa docker start
.
Docker registri
Docker Registry mjesto je za pohranu i distribuciju Dockerovih slika. Docker Images koristit ćemo kao osnovne slike s DockerHub-a, besplatnog registra koji hostira sam Docker.
Docker Compose
Docker Compose alat je koji vam omogućuje izradu i pokretanje više slika Dockera odjednom. Umjesto da pokrećete iste više naredbi svaki put kada želite pokrenuti svoju aplikaciju, sve ih možete izvršiti u jednoj naredbi - nakon što navedete određenu konfiguraciju.
Primjer Dockera s Flaskom i Postgresom
Imajući na umu sve Dockerove komponente, krenimo u postavljanje razvojnog okruženja Dockera s aplikacijom Flask pomoću Postgresa kao spremišta podataka. Za ostatak ovog posta na blogu referencirat ću se Flask Boilerplate, spremište koje sam ranije spomenuo za Hack4Impact.
U ovoj konfiguraciji koristit ćemo Docker za izgradnju dvije slike:
app
- aplikacija tikvice poslužena u luci 5000postgres
- baza podataka Postgres poslužena u luci 5432
Kada pogledate gornji direktorij, postoje tri datoteke koje definiraju ovu konfiguraciju:
- Dockerfile - skripta sastavljena od uputa za postavljanje
app
spremnika. Svaka naredba je automatska i izvršava se sukcesivno. Ova datoteka će se nalaziti u direktoriju gdje ste pokrenuti aplikaciju (python manage.py runserver
ilipython app.py
, ilinpm start
su neki primjeri). U našem se slučaju nalazi u gornjem direktoriju (gdjemanage.py
se nalazi). Dockerfile prihvaća Dockerove upute. - .dockerignore - određuje koje datoteke ne smiju biti uključene u spremnik. To je baš kao i
.gitignore
za Docker kontejnere. Ova je datoteka uparena s Dockerfileom. - docker-compose.yml - Konfiguracijska datoteka za Docker Compose. To će nam omogućiti da istovremeno gradimo
app
ipostgres
slike, i definiramo volumen i stanje kojeapp
ovisi o tomepostgres
i postavljamo potrebne varijable okoline.
Napomena: Postoji samo jedan Dockerfile za dvije slike jer ćemo snimiti službenu Docker Postgres sliku s DockerHub-a! Možete uključiti vlastitu Postgres sliku tako da joj napišete vlastiti Dockerfile, ali nema smisla.
Dockerfile
Samo da pojasnim još jednom, ovaj Dockerfile je za app
spremnik. Kao pregled, ovdje je cijeli Dockerfile - on u osnovi dobiva osnovnu sliku, prekopira aplikaciju, instalira ovisnosti i postavlja određenu varijablu okruženja.
FROM python:3.6
LABEL maintainer "Timothy Ko "
RUN apt-get update
RUN mkdir /app
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
ENV FLASK_ENV="docker"
EXPOSE 5000
Because this Flask Application uses Python 3.6, we want an environment that supports it and already has it installed. Fortunately, DockerHub has an official image that’s installed on top of Ubuntu. In one line, we will have a base Ubuntu image with Python 3.6, virtualenv, and pip. There are tons of images on DockerHub, but if you would like to start off with a fresh Ubuntu image and build on top of it, you could do that.
FROM python:3.6
I then note that I’m the maintainer.
LABEL maintainer "Timothy Ko "
Now it’s time to add the Flask application to the image. For simplicity, I decided to copy the application under the /app
directory on our Docker Image.
RUN mkdir /app
COPY . /app
WORKDIR /app
WORKDIR
is essentially a cd
in bash, and COPY
copies a certain directory to the provided directory in an image. ADD
is another command that does the same thing as COPY
, but it also allows you to add a repository from a URL. Thus, if you want to clone your git repository instead of copying it from your local repository (for staging and production purposes), you can use that. COPY
, however, should be used most of the time unless you have a URL. Every time you use RUN
, COPY
, FROM
, or CMD
, you create a new layer in your docker image, which affects the way Docker stores and caches images. For more information on best practices and layering, see Dockerfile Best Practices.
Now that we have our repository copied to the image, we will install all of our dependencies, which is defined in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
But say you had a Node application instead of Flask — you would instead write RUN npm install
. The next step is to tell Flask to use Docker Configurations that I hardcoded into config.py
. In that configuration, Flask will connect to the correct database we will set up later on. Since I had production and regular development configurations, I made it so that Flask would choose the Docker Configuration whenever the FLASK_ENV
environment variable is set to docker
. So, we need to set that up in our app
image.
ENV FLASK_ENV="docker"
Then, expose the port(5000) the Flask application runs on:
EXPOSE 5000
And that’s it! So no matter what OS you’re on, or how bad you are at following documentation instructions, your Docker image will be same as your team members’ because of this Dockerfile.
Anytime you build your image, these following commands will be run. You can now build this image with sudo docker build -t app .
. However, when you run it with sudo docker run app
to start a Docker Container, the application will run into a database connection error. This is is because you haven’t provisioned a database yet.
docker-compose.yml
Docker Compose will allow you to do that and build your app
image at the same time. The entire file looks like this:
version: '2.1'services: postgres: restart: always image: postgres:10 environment: - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} - POSTGRES_DB=${POSTGRES_DB} volumes: - ./postgres-data/postgres:/var/lib/postgresql/data ports: - "5432:5432" app: restart: always build: . ports: - 5000:5000 volumes: - .:/app
For this specific repository, I decided to use version 2.1 since I was more comfortable with it and it had a few more guides and tutorials on it — yeah, that’s my only reasoning for not using version 3. With version 2, you must provide “services” or images you want to include. In our case, it is app
and postgres
(these are just names that you can refer to when you use docker-compose commands. You call them database
and api
or whatever floats your boat).
Postgres Image
Looking at the Postgres Service, I specify that it is a postgres:10
image, which is another DockerHub Image. This image is an Ubuntu Image that has Postgres installed and will automatically start the Postgres server.
postgres: restart: always image: postgres:10 environment: - POSTGRES_USER=${USER} - POSTGRES_PASSWORD=${PASSWORD} - POSTGRES_DB=${DB} volumes: - ./postgres-data/postgres:/var/lib/postgresql/data ports: - "5432:5432"
If you want a different version, just change the “10” to something else. To specify what user, password, and database you want inside Postgres, you have to define environment variables beforehand — this is implemented in the official postgres Docker image’s Dockerfile. In this case, the postgres
image will inject the $USER
, $PASSWORD
, and $DB
environment variables and make them the POSTGRES_USER
, POSTGRES_PASSWORD
, and POSTGRES_DB
envrionment variables inside the postgres container. Note that $USER
and the other environment variables injected are environment variables specified in your own computer (more specifically the command line process you are using to run the docker-compose up
command. By injecting your credentials, this allows you to not commit your credentials into a public repository.
Docker-compose will also automatically inject environment variables if you have a .env
file in the same directory as your docker-compose.yml
file. Here’s an example of a .env file for this scenario:
USER=testusrPASSWORD=passwordDB=testdb
Thus our PostgreSQL database will be named testdb with a user called testusr with password password.
Our Flask application will connect to this specific database, because I wrote down its URL in the Docker Configurations I mentioned earlier.
Every time a container is stopped and removed, the data is deleted. Thus, you must provide a persistent data storage so none of the database data is deleted. There are two ways to do it:
- Docker Volumes
- Local Directory Mounts
I’ve chosen to mount it locally to ./postgres-data/postgres
, but it can be anywhere. The syntax is always[HOST]:[CONTAINER]
. This means any data from /var/lib/postgresql/data
is actually stored in ./postgres-data
.
volumes:- ./postgres-data/postgres:/var/lib/postgresql/data
We will use the same syntax for ports:
ports:- "5432:5432"
app Image
We will then define the app
image.
app: restart: always build: . ports: - 5000:5000 volumes: - .:/app depends_on: - postgres entrypoint: ["python", "manage.py","runserver"]
We first define it to have restart: always
. This means that it will restart whenever it fails. This is especially useful when we build and start these containers. app
will generally start up before postgres
, meaning that app
will try to connect to the database and fail, since the postgres
isn’t up yet. Without this property, app
would just stop and that’s the end of it.
We then define that we want this build to be the Dockerfile that is in this current directory:
build: .
This next step is pretty important for the Flask server to restart whenever you change any code in your local repository. This is very helpful so you don’t need to rebuild your image over and over again every time to see your changes. To do this, we do the same thing we did for postgres
: we state that the /app
directory inside the container will be whatever is in .(the current directory). Thus, any changes in your local repo will be reflected inside the container.
volumes: - .:/app
After this, we need to tell Docker Compose that app depends on the postgres
container. Note that if you change the name of the image to something else like database
, you must replace that postgres
with that name.
depends_on: - postgres
Finally, we need to provide the command that is called to start our application. In our case, it’s python manage.py runserver
.
entrypoint: ["python", "manage.py","runserver"]
One caveat for Flask is that you must explicitly note which host (port) you want to run it in, and whether you want it to be in debug mode when you run it. So in manage.py
, I do that with:
def runserver(): app.run(debug=True, host=’0.0.0.0', port=5000)
Finally, build and start your Flask app and Postgres Database using your Command Line:
docker-compose builddocker-compose up -ddocker-compose exec app python manage.py recreate_db
The last command essentially creates the database schema defined by my Flask app in Postgres.
And that’s it! You should be able to see the Flask application running on //localhost:5000!
Docker Commands
Remembering and finding Docker commands can be pretty frustrating in the beginning, so here’s a list of them! I’ve also written a bunch of commonly used ones in my Flask Boilerplate Docs if you want to refer to that.
Conclusion
Docker truly allows teams to develop much faster with its portability and consistent environments across platforms. Although I’ve only gone through using Docker for development, Docker excels when you use it for Continuous Integration/testing and in Deployment.
I could add a couple more lines and have a full production setup with Nginx and Gunicorn. If I wanted to use Redis for session caching or as a queue, I could do that very quickly and everyone on my team would be able to have the same environment when they rebuilt their Docker Images.
Not only that, I could spin up 20 instances of the Flask Application in seconds if I wanted to. Thanks for reading! :)
If you have any thoughts and comments, feel free to leave a comment below or email me at [email protected]! Also, feel free to use my code or share this with your peers!