
class: title

# Local development workflow with Docker

![Construction site](images/title-local-development-workflow-with-docker.jpg)

---

## Objectives

At the end of this section, you will be able to:

* Share code between container and host.

* Use a simple local development workflow.

---

## Containerized local development environments

We want to solve the following issues:

- "Works on my machine"

- "Not the same version"

- "Missing dependency"

By using Docker containers, we will get a consistent development environment.

---

## Working on the "namer" application

* We have to work on some application whose code is at:

  https://github.com/jpetazzo/namer.

* What is it? We don't know yet!

* Let's download the code.

```bash
$ git clone https://github.com/jpetazzo/namer
```

---

## Looking at the code

```bash
$ cd namer
$ ls -1
company_name_generator.rb
config.ru
docker-compose.yml
Dockerfile
Gemfile
```

--

Aha, a `Gemfile`! This is Ruby. Probably. We know this. Maybe?

---

## Looking at the `Dockerfile`

```dockerfile
FROM ruby
MAINTAINER Education Team at Docker <education@docker.com>

COPY . /src
WORKDIR /src
RUN bundler install

CMD ["rackup", "--host", "0.0.0.0"]
EXPOSE 9292
```

* This application is using a base `ruby` image.
* The code is copied in `/src`.
* Dependencies are installed with `bundler`.
* The application is started with `rackup`.
* It is listening on port 9292.

---

## Building and running the "namer" application

* Let's build the application with the `Dockerfile`!

--

```bash
$ docker build -t namer .
```

--

* Then run it. *We need to expose its ports.*

--

```bash
$ docker run -dP namer
```

--

* Check on which port the container is listening.

--

```bash
$ docker ps -l
```

---

## Connecting to our application

* Point our browser to our Docker node, on the port allocated to the container.

--

* Hit "reload" a few times.

--

* This is an enterprise-class, carrier-grade, ISO-compliant company name generator!

  (With 50% more bullshit than the average competition!)

  (Wait, was that 50% more, or 50% less? *Anyway!*)

  ![web application 1](images/webapp-in-blue.png)

---

## Making changes to the code

Option 1:

* Edit the code locally
* Rebuild the image
* Re-run the container

Option 2:

* Enter the container (with `docker exec`)
* Install an editor
* Make changes from within the container

Option 3:

* Use a *volume* to mount local files into the container
* Make changes locally
* Changes are reflected into the container

---

## Our first volume

We will tell Docker to map the current directory to `/src` in the container.

```bash
$ docker run -d -v $(pwd):/src -P namer
```

* `-d`: the container should run in detached mode (in the background).

* `-v`: the following host directory should be mounted inside the container.

* `-P`: publish all the ports exposed by this image.

* `namer` is the name of the image we will run.

* We don't specify a command to run because is is already set in the Dockerfile.

---

## Mounting volumes inside containers

The `-v` flag mounts a directory from your host into your Docker container.

The flag structure is:

```bash
[host-path]:[container-path]:[rw|ro]
```

* If `[host-path]` or `[container-path]` doesn't exist it is created.

* You can control the write status of the volume with the `ro` and
  `rw` options.

* If you don't specify `rw` or `ro`, it will be `rw` by default.

There will be a full chapter about volumes!

---

## Testing the development container

* Check the port used by our new container.

```bash
$ docker ps -l
CONTAINER ID  IMAGE  COMMAND  CREATED        STATUS  PORTS                   NAMES
045885b68bc5  namer  rackup   3 seconds ago  Up ...  0.0.0.0:32770->9292/tcp ...
```

* Open the application in your web browser.

---

## Making a change to our application

Our customer really doesn't like the color of our text. Let's change it.

```bash
$ vi company_name_generator.rb
```

And change

```css
color: royalblue;
```

To:

```css
color: red;
```

---

## Viewing our changes

* Reload the application in our browser.

--

* The color should have changed.

  ![web application 2](images/webapp-in-red.png)

---

## Understanding volumes

* Volumes are *not* copying or synchronizing files between the host and the container.

* Volumes are *bind mounts*: a kernel mechanism associating a path to another.

* Bind mounts are *kind of* similar to symbolic links, but at a very different level.

* Changes made on the host or on the container will be visible on the other side.

  (Since under the hood, it's the same file on both anyway.)

---

## Trash your servers and burn your code

*(This is the title of a
[2013 blog post](http://chadfowler.com/2013/06/23/immutable-deployments.html)
by Chad Fowler, where he explains the concept of immutable infrastructure.)*

--

* Let's mess up majorly with our container.

  (Remove files or whatever.)

* Now, how can we fix this?

--

* Our old container (with the blue version of the code) is still running.

* See on which port it is exposed:
  ```bash
  docker ps
  ```

* Point our browser to it to confirm that it still works fine.

---

## Immutable infrastructure in a nutshell

* Instead of *updating* a server, we deploy a new one.

* This might be challenging with classical servers, but it's trivial with containers.

* In fact, with Docker, the most logical workflow is to build a new image and run it.

* If something goes wrong with the new image, we can always restart the old one.

* We can even keep both versions running side by side.

If this pattern sounds interesting, you might want to read about *blue/green deployment*
and *canary deployments*.

---

## Improving the workflow

The workflow that we showed is nice, but it requires us to:

* keep track of all the `docker run` flags required to run the container,

* inspect the `Dockerfile` to know which path(s) to mount,

* write scripts to hide that complexity.

There has to be a better way!

---

## Docker Compose to the rescue

* Docker Compose allows us to "encode" `docker run` parameters in a YAML file.

* Here is the `docker-compose.yml` file that we can use for our "namer" app:

  ```yaml
  www:
    build: .
    volumes:
      - .:/src
    ports:
      - 80:9292
  ```

* Try it:
  ```bash
  $ docker-compose up -d
  ```

---

## Working with Docker Compose

* When you see a `docker-compose.yml` file, you can use `docker-compose up`.

* It can build images and run them with the required parameters.

* Compose can also deal with complex, multi-container apps.

  (More on this later!)

---

## Recap of the development workflow

1. Write a Dockerfile to build an image containing our development environment.
   <br/>
   (Rails, Django, ... and all the dependencies for our app)

2. Start a container from that image.
   <br/>
   Use the `-v` flag to mount our source code inside the container.

3. Edit the source code outside the containers, using regular tools.
   <br/>
   (vim, emacs, textmate...)

4. Test the application.
   <br/>
   (Some frameworks pick up changes automatically.
   <br/>Others require you to Ctrl-C + restart after each modification.)

5. Iterate and repeat steps 3 and 4 until satisfied.

6. When done, commit+push source code changes.

---

class: extra-details

## Debugging inside the container

Docker has a command called `docker exec`.

It allows users to run a new process in a container which is already running.

If sometimes you find yourself wishing you could SSH into a container: you can use `docker exec` instead.

You can get a shell prompt inside an existing container this way, or run an arbitrary process for automation.

---

class: extra-details

## `docker exec` example

```bash
$ # You can run ruby commands in the area the app is running and more!
$ docker exec -it <yourContainerId> bash
root@5ca27cf74c2e:/opt/namer# irb
irb(main):001:0> [0, 1, 2, 3, 4].map {|x| x ** 2}.compact
=> [0, 1, 4, 9, 16]
irb(main):002:0> exit
```

---

class: extra-details

## Stopping the container

Now that we're done let's stop our container.

```bash
$ docker stop <yourContainerID>
```

And remove it.

```bash
$ docker rm <yourContainerID>
```

---

## Section summary

We've learned how to:

* Share code between container and host.

* Set our working directory.

* Use a simple local development workflow.

