Development Workflow
Before we jump to the next section, there’s one last thing I wanted to cover about docker-compose. As stated earlier, docker-compose is really great for development and testing. So let’s see how we can configure compose to make our lives easier during development.
Throughout this tutorial, we’ve worked with readymade docker images. While we’ve built images from scratch, we haven’t touched any application code yet and mostly restricted ourselves to editing Dockerfiles and YAML configurations. One thing that you must be wondering is how does the workflow look during development? Is one supposed to keep creating Docker images for every change, then publish it and then run it to see if the changes work as expected? I’m sure that sounds super tedious. There has to be a better way. In this section, that’s what we’re going to explore.
Let’s see how we can make a change in the Foodtrucks app we just ran. Make sure you have the app running,
$ docker container lsCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES5450ebedd03c prakhar1989/foodtrucks-web "python3 app.py" 9 seconds ago Up 6 seconds 0.0.0.0:5000->5000/tcp foodtrucks_web_105d408b25dfe docker.elastic.co/elasticsearch/elasticsearch:6.3.2 "/usr/local/bin/dock…" 10 hours ago Up 10 hours 0.0.0.0:9200->9200/tcp, 9300/tcp esNow let’s see if we can change this app to display a Hello world! message when a request is made to /hello route. Currently, the app responds with a 404.
$ curl -I 0.0.0.0:5000/helloHTTP/1.0 404 NOT FOUNDContent-Type: text/htmlContent-Length: 233Server: Werkzeug/0.11.2 Python/2.7.15rc1Date: Mon, 30 Jul 2018 15:34:38 GMTWhy does this happen? Since ours is a Flask app, we can see app.py (link) for answers. In Flask, routes are defined with @app.route syntax. In the file, you’ll see that we only have three routes defined - /,/debugand/search. The/route renders the main app, thedebugroute is used to return some debug information and finallysearch is used by the app to query elasticsearch.
$ curl 0.0.0.0:5000/debug{ "msg": "yellow open sfdata Ibkx7WYjSt-g8NZXOEtTMg 5 1 618 0 1.3mb 1.3mb\n", "status": "success"}Given that context, how would we add a new route for hello? You guessed it! Let’s open flask-app/app.py in our favorite editor and make the following change
@app.route('/')def index(): return render_template("index.html")
# add a new hello route@app.route('/hello')def hello(): return "hello world!"Now let’s try making a request again
$ curl -I 0.0.0.0:5000/helloHTTP/1.0 404 NOT FOUNDContent-Type: text/htmlContent-Length: 233Server: Werkzeug/0.11.2 Python/2.7.15rc1Date: Mon, 30 Jul 2018 15:34:38 GMTOh no! That didn’t work! What did we do wrong? While we did make the change in app.py, the file resides in our machine (or the host machine), but since Docker is running our containers based off the prakhar1989/foodtrucks-web image, it doesn’t know about this change. To validate this, lets try the following -
$ docker-compose run web bashStarting es ... doneroot@581e351c82b0:/opt/flask-app# lsapp.py package-lock.json requirements.txt templatesnode_modules package.json static webpack.config.jsroot@581e351c82b0:/opt/flask-app# grep hello app.pyroot@581e351c82b0:/opt/flask-app# exitWhat we’re trying to do here is to validate that our changes are not in the app.py that’s running in the container. We do this by running the command docker-compose run, which is similar to its cousin docker run but takes additional arguments for the service (which is web in our case). As soon as we run bash, the shell opens in /opt/flask-app as specified in our Dockerfile. From the grep command we can see that our changes are not in the file.
Lets see how we can fix it. First off, we need to tell docker compose to not use the image and instead use the files locally. We’ll also set debug mode to true so that Flask knows to reload the server when app.py changes. Replace the web portion of the docker-compose.yml file like so:
version: "3"services: es: image: docker.elastic.co/elasticsearch/elasticsearch:6.3.2 container_name: es environment: - discovery.type=single-node ports: - 9200:9200 volumes: - esdata1:/usr/share/elasticsearch/data web: build: . # replaced image with build command: python3 app.py environment: - DEBUG=True # set an env var for flask depends_on: - es ports: - "5000:5000" volumes: - ./flask-app:/opt/flask-appvolumes: esdata1: driver: localWith that change (diff), let’s stop and start the containers.
$ docker-compose down -vStopping foodtrucks_web_1 ... doneStopping es ... doneRemoving foodtrucks_web_1 ... doneRemoving es ... doneRemoving network foodtrucks_defaultRemoving volume foodtrucks_esdata1
$ docker-compose up -dCreating network "foodtrucks_default" with the default driverCreating volume "foodtrucks_esdata1" with local driverCreating es ... doneCreating foodtrucks_web_1 ... doneAs a final step, lets make the change in app.py by adding a new route. Now we try to curl
$ curl 0.0.0.0:5000/hellohello worldWohoo! We get a valid response! Try playing around by making more changes in the app.
That concludes our tour of Docker Compose. With Docker Compose, you can also pause your services, run a one-off command on a container and even scale the number of containers. I also recommend you checkout a few other use-cases of Docker compose. Hopefully, I was able to show you how easy it is to manage multi-container environments with Compose. In the final section, we are going to deploy our app to AWS!