docker run
The docker run
command groups 4 other commands:
docker image pull # downloads the image from the registry
docker container create # creates the container
docker container start # starts the container
docker container exec # executes a command in a running container
That’s why after each docker run
, it creates a new container.
Using the Object-Oriented Programing paradigm as an analogy, the Image is a Class and the Container is an Object (an instance of the Class).
Less consumption of resources. The container uses the same kernel as the host. Also uses the same libraries and many other resources of the host OS.
Daemon mode vs Interactive mode
The following command
-i
for interactive mode.-t
for attaching a pseudo-terminal to it.docker run -it debian bash
-i, --interactive=true|false
Keep STDIN open even if not attached. The default is false.
When set to true, keep stdin open even if not attached.
-t, --tty=true|false
Allocate a pseudo-TTY. The default is false.
To achieve the same as above with an existing container (without creating a new one):
docker start -ai containerName
-a, --attach[=false] Attach STDOUT/STDERR and forward signals
-i, --interactive[=false] Attach container's STDIN
Providing access via port 8080 to a container running NginX listening on port 80:
docker run -p 8080:80 nginx
-p, --publish ip:[hostPort]:containerPort | [hostPort:]containerPort
Publish a container's port, or range of ports, to the host.
Both hostPort and containerPort can be specified as a range.
When specifying ranges for both, the number of ports in ranges should be equal.
Examples: -p 1234-1236:1222-1224, -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT.
Use docker port(1) to see the actual mapping, e.g. docker port CONTAINER $CONTAINERPORT.
Making a host’s directory accessible inside the container:
docker run -p 8080:80 -v /home/USER/html:/usr/share/nginx/html nginx
-v|--volume[=[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]]
Create a bind mount. If you specify, -v /HOST-DIR:/CONTAINER-DIR, Docker
bind mounts /HOST-DIR in the host to /CONTAINER-DIR in the Docker
container. If 'HOST-DIR' is omitted, Docker automatically creates the new
volume on the host. The OPTIONS are a comma delimited list and can be:
• [rw|ro]
• [z|Z]
• [[r]shared|[r]slave|[r]private]
• [delegated|cached|consistent]
• [nocopy]
Option -d
:
docker run -d --name myNingX -p 8080:80 -v /home/USER/html:/usr/share/nginx/html nginx
-d, --detach=true|false
Detached mode: run the container in the background and print the new container ID.
The default is false.
At any time you can run docker ps in the other shell to view a list of the running con‐
tainers. You can reattach to a detached container with docker attach.
When attached in the tty mode, you can detach from the container (and leave it running)
using a configurable key sequence. The default sequence is CTRL-p CTRL-q. You config‐
ure the key sequence using the --detach-keys option or a configuration file. See con‐
fig-json(5) for documentation on using a configuration file.
docker inspect containerName
docker exec containerName COMMAND
docker logs containerName
docker image
pull
ls
rm
inspect
tag
build
push
Docker Registry: a server-side application to store and distribute Docker images.
Docker Hub: a cloud docker image registry service, allowing association with repositories to image building automatization.
The docker hub offers a public docker registry.
An example with nginx.
Create a new directory, go inside of it and then create a Dockerfile
:
FROM nginx:latest
RUN echo '<h1>Hello meleu!</h1>' > /usr/share/nginx/html/index.html
And then run the command in the same directory as the Dockerfile
:
docker image build --tag nginx-hello-meleu .
Now you have your custom image, and you can see it with this command
docker image ls
And run it like this:
docker container run --publish 80:80 nginx-hello-meleu
Create a new directory, go inside of it and then create a Dockerfile
:
FROM debian
LABEL maintainer 'meleu <meleu@meleu.dev>'
ARG S3_BUCKET=files
ENV S3_BUCKET=${S3_BUCKET}
This allows us to provide a custom S3_BUCKET
via command line.
Let’s create the image by running this command in the same directory as the Dockerfile
:
docker image build \
--build-arg S3_BUCKET='myApp' \
--tag nginx-hello-meleu .
Run the following to check if the variable is different than the default one:
docker container run nginx-hello-meleu bash -c 'echo "$S3_BUCKET"'
Dockerfile
:
FROM nginx:latest
LABEL maintainer 'meleu <meleu@meleu.dev>'
COPY *.html /usr/share/nginx/html/
Inside the same directory, let’s assume we have this index.html
:
<h1>This file is originally in the host OS</h1>
Create the image:
docker image build --tag nginx-copy-file .
Run it:
docker container run --publish 80:80 nginx-copy-file
In this example we’re going to use a Python web server. The file is named run.py
:
import logging
import http.server
import socketserver
import getpass
class MyHTTPHandler(http.server.SimpleHTTPRequestHandler):
def log_message(self, format, *args):
logging.info("%s - - [%s] %s\n"% (
self.client_address[0],
self.log_date_time_string(),
format%args
))
logging.basicConfig(
filename='/log/http-server.log',
format='%(asctime)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logging.getLogger().addHandler(logging.StreamHandler())
logging.info('starting...')
PORT = 8000
httpd = socketserver.TCPServer(("", PORT), MyHTTPHandler)
logging.info('listening on port: %s', PORT)
logging.info('user: %s', getpass.getuser())
httpd.serve_forever()
Note: in the code above we’re creating a log in the file /log/http-server.log
.
We’ll also need a simple index.html
:
<h1>Hello from Python!</h1>
Dockerfile
FROM python:3.6
LABEL maintainer 'meleu <meleu at meleu.dev>'
RUN useradd www && \
mkdir /app && \
mkdir /log && \
chown www /log
USER www
VOLUME /log
WORKDIR /app
EXPOSE 8000
ENTRYPOINT ["/usr/local/bin/python"]
CMD ["run.py"]
Note: the volume that will be available for external containers will be the /log
.
Building the image:
docker image build --tag python-web-server
Running the container:
docker container run \
--interactive \
--tty \
--volume "$(pwd)":/app \
--publish 80:8000 \
--name python-server \
python-web-server
Now we’re going to run another container and get access to the volume created in the container above:
docker container run \
--interactive \
--tty \
--volumes-from python-server \
debian \
cat /log/http-server.log
create an account at https://hub.docker.com.
docker image tag ex-simple-build USER_NAME/IMAGE_BUILD_NAME:1.0
docker login --username=USER_NAME
docker image push USER_NAME/IMAGE_BUILD_NAME:1.0
Default networking model (bridge network):
,-------------, ,-------------,
| Container A | | Container B |
|-------------| |-------------|
| net.interf. | | net.interf. |
'-------------' '-------------'
^ ^
| |
v v
,-----------------------------,
| Bridge (docker0) |
'-----------------------------'
,-----------------------------,
| host |
'-----------------------------'
^
|
v
internet
Networking types:
commands:
docker network ls
$ docker container run --rm alpine ash -c ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:2 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:180 (180.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
$ docker container run --net none --rm alpine ash -c ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
$ docker network inspect bridge
[
{
"Name": "bridge",
"Id": "62015cfacbd4491f71d72d2edc2abdea62466c5351d87373ad64ad8cfb7c69b6",
"Created": "2020-12-12T16:36:28.31057956-03:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
Creating two containers and ping one each other:
$ docker container run -d --name container1 alpine sleep 1000
b65cf2f729bdd6c0b35fa057c7ac253980b2ca852fe49c7eb09c00a78ca5f67b
$ docker container exec -it container1 ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:21 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3128 (3.0 KiB) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
$ docker container run -d --name container2 alpine sleep 1000
d330529c17f5a3c3a7073755a17be2c8c3b0e37df2744770ca3d6d61271c1836
$ docker container exec -it container2 ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:03
inet addr:172.17.0.3 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:20 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2978 (2.9 KiB) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
$ docker container exec -it container2 ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.097 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.122 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.128 ms
...
Creating a new network separated from the default one:
$ docker network create --driver bridge NewNetwork
4efc2b0ce69807aa4fe068715b2e60b5bbbbfa18a58f4682a7682029112f0e6c
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
4efc2b0ce698 NewNetwork bridge local
62015cfacbd4 bridge bridge local
38729066bc9d host host local
9a7fdf6bef68 none null local
$ docker network inspect NewNetwork
[
{
"Name": "NewNetwork",
"Id": "4efc2b0ce69807aa4fe068715b2e60b5bbbbfa18a58f4682a7682029112f0e6c",
"Created": "2020-12-15T18:04:10.565305611-03:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
Note the network is now 172.18.0.0 (not 172.17.0.0).
Now let’s create a new container connected to the NewNetwork and check if we can ping the two containers in the default bridge network:
$ docker container run -d --name container3 --net NewNetwork alpine sleep 1000
c2a20307341bef4b04ed5902fbcb6efc5b0677eb0980813391f851f6604b8608
$ docker container exec -it container3 ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:12:00:02
inet addr:172.18.0.2 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:47 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:7454 (7.2 KiB) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
$ docker container exec -it container3 ping 172.17.0.2 # <-- ip of container1
PING 172.17.0.2 (172.17.0.2): 56 data bytes
^C
--- 172.17.0.2 ping statistics ---
30 packets transmitted, 0 packets received, 100% packet loss
We couldn’t reach container1 from container3 because they’re connected to different networks.
Now let’s connect container3 to the default bridge network:
$ docker network connect bridge container3
$ docker container exec -it container3 ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:12:00:02
inet addr:172.18.0.2 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:62 errors:0 dropped:0 overruns:0 frame:0
TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:9956 (9.7 KiB) TX bytes:336 (336.0 B)
eth1 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:14 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1996 (1.9 KiB) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
$ docker container exec -it container3 ping 172.17.0.2 # <-- ip of container1
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.071 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.107 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.108 ms
64 bytes from 172.17.0.2: seq=3 ttl=64 time=0.108 ms
64 bytes from 172.17.0.2: seq=4 ttl=64 time=0.108 ms
^C
--- 172.17.0.2 ping statistics ---
5 packets transmitted, 5 packets received, 0% packet loss
round-trip min/avg/max = 0.071/0.100/0.108 ms
$ # let's disconnect container3 from the default bridge network...
$ docker network disconnect bridge container3
$ docker container exec -it container3 ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:12:00:02
inet addr:172.18.0.2 Bcast:172.18.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:62 errors:0 dropped:0 overruns:0 frame:0
TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:9956 (9.7 KiB) TX bytes:336 (336.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:10 errors:0 dropped:0 overruns:0 frame:0
TX packets:10 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:840 (840.0 B) TX bytes:840 (840.0 B)
$ docker container exec -it container3 ping 172.17.0.2 # <-- ip of container1
PING 172.17.0.2 (172.17.0.2): 56 data bytes
^C
--- 172.17.0.2 ping statistics ---
5 packets transmitted, 0 packets received, 100% packet loss
Giving to the container direct access to the host’s interfaces.
Example:
$ docker container run -d --name container4 --net host alpine sleep 1000
$ docker container exec -it container4 ifconfig
# it shows all the interfaces you have in the host OS
Basically:
# check the latest release version: https://github.com/docker/compose/releases
sudo curl -L \
"https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
Example
File structure (https://github.com/cod3rcursos/curso-docker/node-mongo-compose):
node-mongo-compose/
├── backend
│ ├── app.js
│ ├── package.json
│ └── package-lock.json
├── docker-compose.yml
└── frontend
└── index.html
docker-compose.yml
:
version: '3'
services:
db:
image: mongo:3.4
backend:
image: node:8.1
volumes:
- ./backend:/backend
ports:
- 3000:3000
command: bash -c "cd /backend && npm i && node app"
frontend:
image: nginx:1.13
volumes:
- ./frontend:/usr/share/nginx/html/
ports:
- 80:80
And then run:
docker-compose up
mkdir email-worker-compose
cd email-worker-compose
docker-compose.yml
:
version: '3' # versao do docker-compose
services:
db:
image: postgres:9.6
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
commands:
docker-compose up -d # daemon mode
docker-compose ps
docker-compose exec db psql -U postgres -c '\l'
Create the following files:
scripts/init.sql
:
create database email_sender;
\c email_sender
create table emails (
id serial not null,
data timestamp not null default current_timestamp,
assunto varchar(100) not null,
mensagem varchar(250) not null
);
scripts/check.sql
:
\l
\c email_sender
\d emails
And edit the docker-compose.yml
:
version: '3' # versao do docker-compose
volumes:
dados:
services:
db:
image: postgres:9.6
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
volumes:
# volume dos dados
- dados:/var/lib/postgresql/data
# scripts
- ./scripts:/scripts
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql
# Check <https://hub.docker.com/_/postgres> - "Initialization scripts"
And then:
docker-compose down # assure we're starting fresh
docker-compose up -d
docker-compose ps
docker-compose exec db psql -U postgres -f /scripts/check.sql
web/index.html
:
<html>
<head>
<meta charset='uft-8'>
<title>E-mail Sender</title>
<style>
label { display: block; }
textarea, input { width: 400px; }
</style>
</head>
<body class="container">
<h1>E-mail Sender</h1>
<form action="http://localhost:8080 method="POST">
<div>
<label for="assunto">Assunto</label>
<input type="text" name="assunto">
</div>
<div>
<label for="mensagem">Mensagem</label>
<textarea name="mensagem" cols="50" rows="6"></textarea>
</div>
<div>
<button>Enviar !</button>
</div>
</form>
</body>
</html>
docker-compose.yml
:
# services: ...
# ...
frontend:
image: nginx:1.13
volumes:
# site
- ./web:/usr/share/nginx/html/
ports:
- 80:80
Testing:
docker-compose down # assure we're starting fresh
docker-compose up -d
docker-compose ps
docker-compose logs -f -t
# browser http://localhost/
Initially we’re going to allow the app server to be contacted directly, via port 8080. Later we’ll use a reverse proxy.
app/app.sh
:
#!/bin/sh
pip install bottle==0.12.13
python -u sender.py
app/sender.py
:
from bottle import route, run, request
@route('/', method='POST')
def send():
assunto = request.forms.get('assunto')
mensagem = request.forms.get('mensagem')
return 'Mensagem enfileirada ! Assunto: {} Mensagem: {}'.format(
assunto, mensagem
)
if __name__ == '__main__':
run(host='0.0.0.0', port=8080, debug=True)
docker-compose.yml
:
# services: ...
# ...
app:
image: python:3.6
volumes:
# application
- ./app:/app
working_dir: /app
command: bash ./app.sh
ports:
- 8080:8080
Testing:
docker-compose down # assure we're starting fresh
docker-compose up -d
docker-compose ps
docker-compose logs -f -t
# browser http://localhost/
Adding a config in the frontend to act like a reverse proxy to the backend app, so we don’t need to allow direct access to the backend (increase security).
nginx/default.conf
:
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location /api {
proxy_pass http://app:8080/;
# the "app" address is the service name used in the docker-compose.yml
proxy_http_version 1.1;
}
}
web/index.html
- <form action="http://localhost:8080" method="POST">
+ <form action="http://localhost/api" method="POST">
docker-compose.yml
services:
frontend:
+ # reverse proxy config
+ - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
app:
- ports:
- - 8080:8080
Testing:
docker-compose down # assure we're starting fresh
docker-compose up -d
docker-compose ps
docker-compose logs -f -t
# browser http://localhost/
We’re going to have a network for the web frontend and a network for the database. And the app is going to be connected to both.
docker-compose.yml
:
networks:
banco:
web:
services:
db:
+ networks:
+ - banco
frontend:
+ networks:
+ - web
+ depends_on:
+ - app
app:
+ networks:
+ - web
+ - banco
+ depends_on:
+ - db
app/app.sh
:
#!/bin/sh
pip install bottle==0.12.13 psycopg2==2.7.3.2
python -u sender.py
app/sender.py
:
import psycopg2
from bottle import route, run, request
DSN = 'dbname=email_sender user=postgres host=db'
SQL = 'INSERT INTO emails (assunto, mensagem) VALUES (%s, %s)'
def register_message(assunto, mensagem):
conn = psycopg2.connect(DSN)
cur = conn.cursor()
cur.execute(SQL, (assunto, mensagem))
conn.commit()
cur.close()
conn.close()
print('Mensagem registrada!')
@route('/', method='POST')
def send():
assunto = request.forms.get('assunto')
mensagem = request.forms.get('mensagem')
register_message(assunto, mensagem)
return 'Mensagem enfileirada ! Assunto: {} Mensagem: {}'.format(
assunto, mensagem
)
if __name__ == '__main__':
run(host='0.0.0.0', port=8080, debug=True)
Testing:
docker-compose down # assure we're starting fresh
docker-compose up -d
docker-compose ps
docker-compose logs -f -t
# browser http://localhost/
docker-compose exec db psql -U postgres -d email_sender -c 'select * from emails'
Add a new network named fila
, and two new services using that network: queue
and worker
.
The service app
is also connected to the fila
network.
docker-compose.yml
:
networks:
banco:
web:
+ fila:
services:
app:
networks:
- web
- banco
+ - fila
depends_on:
- db
+ - queue
+ queue:
+ image: redis:3.2
+ networks:
+ - fila
+
+ worker:
+ image: python:3.6
+ volumes:
+ # worker
+ - ./worker:/worker
+ working_dir: /worker
+ command: bash ./app.sh
+ networks:
+ - fila
+ depends_on:
+ - queue
worker/app.sh
:
#!/bin/sh
pip install redis==2.10.5
python -u worker.py
worker/worker.py
:
import redis
import json
from time import sleep
from random import randint
if __name__ == '__main__':
r = redis.Redis(host='queue', port=6379, db=0)
while True:
mensagem = json.loads(r.blpop('sender')[1])
# simulando envio de email...
print ('Mandando a mensagem: ', mensagem['assunto'])
sleep(randint(15, 45))
print('Mensagem', mensagem['assunto'], 'enviada')
app/app.sh
#!/bin/sh
pip install bottle==0.12.13 psycopg2==2.7.3.2 redis==2.10.5
python -u sender.py
app/sender.py
:
import psycopg2
import redis
import json
from bottle import Bottle, request
class Sender(Bottle):
def __init__(self):
super().__init__()
self.route('/', method='POST', callback=self.send)
self.fila = redis.StrictRedis(host='queue', port=6379, db=0)
DSN = 'dbname=email_sender user=postgres host=db'
self.conn = psycopg2.connect(DSN)
def register_message(self, assunto, mensagem):
SQL = 'INSERT INTO emails (assunto, mensagem) VALUES (%s, %s)'
cur = self.conn.cursor()
cur.execute(SQL, (assunto, mensagem))
self.conn.commit()
cur.close()
msg = {'assunto': assunto, 'mensagem': mensagem}
self.fila.rpush('sender', json.dumps(msg))
print('Mensagem registrada !')
def send(self):
assunto = request.forms.get('assunto')
mensagem = request.forms.get('mensagem')
self.register_message(assunto, mensagem)
return 'Mensagem enfileirada ! Assunto: {} Mensagem: {}'.format(
assunto, mensagem
)
if __name__ == '__main__':
sender = Sender()
sender.run(host='0.0.0.0', port=8080, debug=True)