|
| 1 | +--- |
| 2 | +title: "Getting Started, Part 2: Creating and Building Your App" |
| 3 | +--- |
| 4 | + |
| 5 | +In [Getting Started, Part 1: Orientation and Setup](index.md), you heard an |
| 6 | +overview of what containers are, what the |
| 7 | +Docker platform does, and what we'll be covering in this multi-part tutorial. |
| 8 | +You also got Docker installed on your machine. |
| 9 | + |
| 10 | +In this section, you will write, build, run, and share an app, the Docker way. |
| 11 | + |
| 12 | +## Your development environment |
| 13 | + |
| 14 | +In the past, if you were to start writing a Python app, your first |
| 15 | +order of business was to install a Python runtime onto your machine. But, |
| 16 | +that creates a situation where the environment on your machine has to be just |
| 17 | +so in order for your app to run as expected; ditto for the server that runs |
| 18 | +your app. |
| 19 | + |
| 20 | +With Docker, you can just grab a portable Python runtime as an image, no |
| 21 | +installation necessary. Then, your build can include the base Python image |
| 22 | +right alongside your app code, ensuring that your app, its dependencies, and the |
| 23 | +runtime, all travel together. |
| 24 | + |
| 25 | +These builds are configured with something called a `Dockerfile`. |
| 26 | + |
| 27 | +## Your first Dockerfile |
| 28 | + |
| 29 | +Create an empty directory and put this file in it, with the name `Dockerfile`. |
| 30 | +`Dockerfile` will define what goes on in the environment inside your |
| 31 | +container. Access to resources like networking interfaces and disk drives is |
| 32 | +virtualized inside this environment, which is isolated from the rest of your |
| 33 | +system, so you have to map ports to the outside world, and |
| 34 | +be specific about what files you want to "copy in" to that environment. However, |
| 35 | +after doing that, you can expect that the build of your app defined in this |
| 36 | +`Dockerfile` will behave exactly the same wherever it runs. |
| 37 | + |
| 38 | +{% gist johndmulhausen/c31813e076827178216b74e6a6f4a087 %} |
| 39 | + |
| 40 | +This `Dockerfile` refers to a couple of things we haven't created yet, namely |
| 41 | +`app.py` and `requirements.txt`. We'll get there. But here's what this |
| 42 | +`Dockerfile` is saying: |
| 43 | + |
| 44 | +- Download the official image of the Python 2.7 runtime and include it here. |
| 45 | +- Create `/app` and set it as the current working directory inside the container |
| 46 | +- Copy the contents of the current directory on my machine into `/app` inside the container |
| 47 | +- Install any Python packages that I list inside `requirements.txt` |
| 48 | +- Ensure that port 80 is exposed to the world outside this container |
| 49 | +- Set an environment variable within this container named `NAME` to be the string `World` |
| 50 | +- Finally, execute `python` and pass in `app.py` as the "entry point" command, |
| 51 | + the default command that is executed at runtime. |
| 52 | + |
| 53 | +### The app itself |
| 54 | + |
| 55 | +Grab these two files and place them in the same folder as `Dockerfile`. |
| 56 | + |
| 57 | +{% gist johndmulhausen/074cc7f4c26a9a8f9164b20b22602ad7 %} |
| 58 | +{% gist johndmulhausen/8728902faede400c057f3205392bb9a8 %} |
| 59 | + |
| 60 | +Now we see that the `Dockerfile` command `pip install requirements.txt` installs |
| 61 | +the Flask and Redis libraries for Python. We can also see that app itself |
| 62 | +prints the environment variable of `NAME`, which we set as `World`, as well as |
| 63 | +the output of a call to `socket.gethostname()`, which the Docker runtime is |
| 64 | +going to answer with the container ID, which is sort of like the process ID for |
| 65 | +an executable. Finally, because Redis isn't running |
| 66 | +(as we've only installed the Python library, and not Redis itself), we should |
| 67 | +expect that the attempt to use it here will fail and produce the error message. |
| 68 | + |
| 69 | +## Build the App |
| 70 | + |
| 71 | +That's it! You don't need to have installed Python or anything in |
| 72 | +`requirements.txt` on your system, nor will running this app install them in |
| 73 | +your system. It doesn't seem like you've really set up an environment with |
| 74 | +Python and Flask, but you have. Let's build and run your app and prove it. |
| 75 | + |
| 76 | +7Here's what `ls` should show: |
| 77 | + |
| 78 | +```shell |
| 79 | +$ ls |
| 80 | +Dockerfile app.py requirements.txt |
| 81 | +``` |
| 82 | + |
| 83 | +Now run the build command. This creates a Docker image, which we're going to |
| 84 | +tag using `-t` so it has a friendly name. |
| 85 | + |
| 86 | +```shell |
| 87 | +docker build -t friendlyhello . |
| 88 | +``` |
| 89 | + |
| 90 | +In the output spew you can see everything defined in the `Dockerfile` happening. |
| 91 | +Where is your built image? It's in your machine's local Docker image registry. |
| 92 | +Check it out: |
| 93 | + |
| 94 | +```shell |
| 95 | +$ docker images |
| 96 | +REPOSITORY TAG IMAGE ID CREATED SIZE |
| 97 | +friendlyhello latest 326387cea398 47 seconds ago 192.1 MB |
| 98 | +``` |
| 99 | + |
| 100 | +## Run the app |
| 101 | + |
| 102 | +Run the app, mapping our machine's port 4000 to the container's exposed port 80 |
| 103 | +using `-p`: |
| 104 | + |
| 105 | +```shell |
| 106 | +docker run -p 4000:80 friendlyhello |
| 107 | +``` |
| 108 | + |
| 109 | +You should see a notice that Python is serving your app at `http://0.0.0.0:80`. |
| 110 | +But that message coming from inside the container, which doesn't know you |
| 111 | +actually want to access your app at: `http://localhost:4000`. Go there, and |
| 112 | +you'll see the "Hello World" text, the container ID, and the Redis error |
| 113 | +message, all printed out in beautiful Times New Roman. |
| 114 | + |
| 115 | +Hit `CTRL+C` in your terminal to quit. |
| 116 | + |
| 117 | +Now let's run the app in the background, in detached mode: |
| 118 | + |
| 119 | +```shell |
| 120 | +docker run -d -p 4000:80 friendlyhello |
| 121 | +``` |
| 122 | + |
| 123 | +You get a hash ID of the container instance and then are kicked back to your |
| 124 | +terminal. Your app is running in the background. Let's see it with `docker ps`: |
| 125 | + |
| 126 | +```shell |
| 127 | +$ docker ps |
| 128 | +CONTAINER ID IMAGE COMMAND CREATED STATUS |
| 129 | +1fa4ab2cf395 friendlyhello "python app.py" 28 seconds ago Up 25 seconds |
| 130 | +``` |
| 131 | + |
| 132 | +You'll see that `CONTAINER ID` matches what's on `http://localhost:4000`, if you |
| 133 | +refresh the browser page. Now use `docker stop` to end the process, using |
| 134 | +`CONTAINER ID`, like so: |
| 135 | + |
| 136 | +```shell |
| 137 | +docker stop 1fa4ab2cf395 |
| 138 | +``` |
| 139 | + |
| 140 | +## Share your image |
| 141 | + |
| 142 | +Sign up a Docker account at [hub.docker.com](https://hub.docker.com/). |
| 143 | +Make note of your username. We're going to use it in a couple commands. |
| 144 | + |
| 145 | +Docker Hub is a public registry. A registry is a collection of accounts and |
| 146 | +their various repositories. A repository is a collection of tagged images like a |
| 147 | +GitHub repository, except the code is already built. |
| 148 | + |
| 149 | +Log in your local machine to Docker Hub. |
| 150 | + |
| 151 | +```shell |
| 152 | +docker login |
| 153 | +``` |
| 154 | + |
| 155 | +Now, let's publish your image. First, specify the repository you'd like to use |
| 156 | +in a tag. The notation for associating a local image with a repository on a |
| 157 | +registry, is `username/repository:tag`. The `:tag` is optional, but recommended; |
| 158 | +it's the mechnism that registries use to give Docker images a version. So, |
| 159 | +putting all that together: |
| 160 | + |
| 161 | +```shell |
| 162 | +docker tag friendlyhello YOURUSERNAME/YOURREPO:ARBITRARYTAG |
| 163 | +``` |
| 164 | + |
| 165 | +From now on, you can use `docker run` on this machine with the fully qualified |
| 166 | +tag. But that won't work on other machines until you upload this image, like so: |
| 167 | + |
| 168 | +```shell |
| 169 | +docker push YOURUSERNAME/YOURREPO:ARBITRARYTAG |
| 170 | +``` |
| 171 | + |
| 172 | +Once complete, the results of this upload are [publicly available |
| 173 | +on Docker Hub](https://hub.docker.com/). |
| 174 | + |
| 175 | +Now, remembering whatever you specified as your target repo, and whatever you |
| 176 | +used as a tag, go on another machine. Any machine where you can install Docker, |
| 177 | +and run this command: |
| 178 | + |
| 179 | +```shell |
| 180 | +docker run YOURUSERNAME/YOURREPO:ARBITRARYTAG |
| 181 | +``` |
| 182 | + |
| 183 | +> Note: If you don't specify the `:ARBITRARYTAG` portion of these commands, |
| 184 | + the tag of `:latest` will be assumed, both when you build and when you run |
| 185 | + images. |
| 186 | + |
| 187 | +You'll see this stranger of a machine pull your image, along with Python and all |
| 188 | +the dependencies from `requirements.txt`, and run your code. It all travels |
| 189 | +together in a neat little package, and the new machine didn't have to install |
| 190 | +anything but Docker to run it. |
| 191 | + |
| 192 | +## Recap and cheat sheet for images and containers |
| 193 | + |
| 194 | +To recap: After calling `docker run`, you created and ran a container, based on |
| 195 | +the image created when you called `docker build`. Images are defined in a |
| 196 | +`Dockerfile`. A container is an instance of an image, and it has any package |
| 197 | +installations, file writes, etc that happen after you call `docker run` and run |
| 198 | +the app. And lastly, images are shared via a registry. |
| 199 | + |
| 200 | +```shell |
| 201 | +docker build -t friendlyname . #Create image using this directory's Dockerfile |
| 202 | +docker run -p 4000:80 friendlyname #Run image "friendlyname" mapping port 4000 to 80 |
| 203 | +docker run -d -p 4000:80 friendlyname #Same thing, but in detached mode |
| 204 | +docker ps #See a list of all running containers |
| 205 | +docker stop <hash> #Gracefully stop the specified container |
| 206 | +docker ps -a #See a list of all containers on this machine, even the ones not running |
| 207 | +docker kill <hash> #Force shutdown of the specified container |
| 208 | +docker rm <hash> #Remove the specified container from this machine |
| 209 | +docker rm $(docker ps -a -q) #Remove all containers from this machine |
| 210 | +docker images -a #Show all images that have been built or downloaded onto this machine |
| 211 | +docker rmi <imagename> #Remove the specified image from this machine |
| 212 | +docker rmi $(docker images -q) #Remove all images from this machine |
| 213 | +docker login #Log in this CLI session using your Docker credentials (to Docker Hub by default) |
| 214 | +docker tag <image> username/repository:tag #Tag <image> on your local machine for upload |
| 215 | +docker push username/repository:tag #Upload tagged image to registry (Docker Hub by default) |
| 216 | +docker run username/repository:tag #Run image from a registry (Docker Hub by default) |
| 217 | +``` |
| 218 | + |
| 219 | +[On to "Getting Started, Part 3: Stateful, Multi-container Applications" >>](part3.md){: class="button darkblue-btn"} |
0 commit comments