mirror of https://github.com/docker/docs.git
222 lines
9.0 KiB
Markdown
222 lines
9.0 KiB
Markdown
---
|
|
title: "Getting Started, Part 2: Creating and Building Your App"
|
|
---
|
|
|
|
# Getting Started, Part 2: Creating and Building Your App
|
|
|
|
In [Getting Started, Part 1: Orientation and Setup](index.md), you heard an
|
|
overview of what containers are, what the
|
|
Docker platform does, and what we'll be covering in this multi-part tutorial.
|
|
You also got Docker installed on your machine.
|
|
|
|
In this section, you will write, build, run, and share an app, the Docker way.
|
|
|
|
## Your development environment
|
|
|
|
In the past, if you were to start writing a Python app, your first
|
|
order of business was to install a Python runtime onto your machine. But,
|
|
that creates a situation where the environment on your machine has to be just
|
|
so in order for your app to run as expected; ditto for the server that runs
|
|
your app.
|
|
|
|
With Docker, you can just grab a portable Python runtime as an image, no
|
|
installation necessary. Then, your build can include the base Python image
|
|
right alongside your app code, ensuring that your app, its dependencies, and the
|
|
runtime, all travel together.
|
|
|
|
These builds are configured with something called a `Dockerfile`.
|
|
|
|
## Your first Dockerfile
|
|
|
|
Create an empty directory and put this file in it, with the name `Dockerfile`.
|
|
`Dockerfile` will define what goes on in the environment inside your
|
|
container. Access to resources like networking interfaces and disk drives is
|
|
virtualized inside this environment, which is isolated from the rest of your
|
|
system, so you have to map ports to the outside world, and
|
|
be specific about what files you want to "copy in" to that environment. However,
|
|
after doing that, you can expect that the build of your app defined in this
|
|
`Dockerfile` will behave exactly the same wherever it runs.
|
|
|
|
{% gist johndmulhausen/c31813e076827178216b74e6a6f4a087 %}
|
|
|
|
This `Dockerfile` refers to a couple of things we haven't created yet, namely
|
|
`app.py` and `requirements.txt`. We'll get there. But here's what this
|
|
`Dockerfile` is saying:
|
|
|
|
- Download the official image of the Python 2.7 runtime and include it here.
|
|
- Create `/app` and set it as the current working directory inside the container
|
|
- Copy the contents of the current directory on my machine into `/app` inside the container
|
|
- Install any Python packages that I list inside `requirements.txt`
|
|
- Ensure that port 80 is exposed to the world outside this container
|
|
- Set an environment variable within this container named `NAME` to be the string `World`
|
|
- Finally, execute `python` and pass in `app.py` as the "entry point" command,
|
|
the default command that is executed at runtime.
|
|
|
|
### The app itself
|
|
|
|
Grab these two files and place them in the same folder as `Dockerfile`.
|
|
|
|
{% gist johndmulhausen/074cc7f4c26a9a8f9164b20b22602ad7 %}
|
|
{% gist johndmulhausen/8728902faede400c057f3205392bb9a8 %}
|
|
|
|
Now we see that the `Dockerfile` command `pip install requirements.txt` installs
|
|
the Flask and Redis libraries for Python. We can also see that app itself
|
|
prints the environment variable of `NAME`, which we set as `World`, as well as
|
|
the output of a call to `socket.gethostname()`, which the Docker runtime is
|
|
going to answer with the container ID, which is sort of like the process ID for
|
|
an executable. Finally, because Redis isn't running
|
|
(as we've only installed the Python library, and not Redis itself), we should
|
|
expect that the attempt to use it here will fail and produce the error message.
|
|
|
|
## Build the App
|
|
|
|
That's it! You don't need to have installed Python or anything in
|
|
`requirements.txt` on your system, nor will running this app install them in
|
|
your system. It doesn't seem like you've really set up an environment with
|
|
Python and Flask, but you have. Let's build and run your app and prove it.
|
|
|
|
7Here's what `ls` should show:
|
|
|
|
```shell
|
|
$ ls
|
|
Dockerfile app.py requirements.txt
|
|
```
|
|
|
|
Now run the build command. This creates a Docker image, which we're going to
|
|
tag using `-t` so it has a friendly name.
|
|
|
|
```shell
|
|
docker build -t friendlyhello .
|
|
```
|
|
|
|
In the output spew you can see everything defined in the `Dockerfile` happening.
|
|
Where is your built image? It's in your machine's local Docker image registry.
|
|
Check it out:
|
|
|
|
```shell
|
|
$ docker images
|
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
friendlyhello latest 326387cea398 47 seconds ago 192.1 MB
|
|
```
|
|
|
|
## Run the app
|
|
|
|
Run the app, mapping our machine's port 4000 to the container's exposed port 80
|
|
using `-p`:
|
|
|
|
```shell
|
|
docker run -p 4000:80 friendlyhello
|
|
```
|
|
|
|
You should see a notice that Python is serving your app at `http://0.0.0.0:80`.
|
|
But that message coming from inside the container, which doesn't know you
|
|
actually want to access your app at: `http://localhost:4000`. Go there, and
|
|
you'll see the "Hello World" text, the container ID, and the Redis error
|
|
message, all printed out in beautiful Times New Roman.
|
|
|
|
Hit `CTRL+C` in your terminal to quit.
|
|
|
|
Now let's run the app in the background, in detached mode:
|
|
|
|
```shell
|
|
docker run -d -p 4000:80 friendlyhello
|
|
```
|
|
|
|
You get a hash ID of the container instance and then are kicked back to your
|
|
terminal. Your app is running in the background. Let's see it with `docker ps`:
|
|
|
|
```shell
|
|
$ docker ps
|
|
CONTAINER ID IMAGE COMMAND CREATED STATUS
|
|
1fa4ab2cf395 friendlyhello "python app.py" 28 seconds ago Up 25 seconds
|
|
```
|
|
|
|
You'll see that `CONTAINER ID` matches what's on `http://localhost:4000`, if you
|
|
refresh the browser page. Now use `docker stop` to end the process, using
|
|
`CONTAINER ID`, like so:
|
|
|
|
```shell
|
|
docker stop 1fa4ab2cf395
|
|
```
|
|
|
|
## Share your image
|
|
|
|
Sign up a Docker account at [hub.docker.com](https://hub.docker.com/).
|
|
Make note of your username. We're going to use it in a couple commands.
|
|
|
|
Docker Hub is a public registry. A registry is a collection of accounts and
|
|
their various repositories. A repository is a collection of tagged images like a
|
|
GitHub repository, except the code is already built.
|
|
|
|
Log in your local machine to Docker Hub.
|
|
|
|
```shell
|
|
docker login
|
|
```
|
|
|
|
Now, let's publish your image. First, specify the repository you'd like to use
|
|
in a tag. The notation for associating a local image with a repository on a
|
|
registry, is `username/repository:tag`. The `:tag` is optional, but recommended;
|
|
it's the mechnism that registries use to give Docker images a version. So,
|
|
putting all that together:
|
|
|
|
```shell
|
|
docker tag friendlyhello YOURUSERNAME/YOURREPO:ARBITRARYTAG
|
|
```
|
|
|
|
From now on, you can use `docker run` on this machine with the fully qualified
|
|
tag. But that won't work on other machines until you upload this image, like so:
|
|
|
|
```shell
|
|
docker push YOURUSERNAME/YOURREPO:ARBITRARYTAG
|
|
```
|
|
|
|
Once complete, the results of this upload are [publicly available
|
|
on Docker Hub](https://hub.docker.com/).
|
|
|
|
Now, remembering whatever you specified as your target repo, and whatever you
|
|
used as a tag, go on another machine. Any machine where you can install Docker,
|
|
and run this command:
|
|
|
|
```shell
|
|
docker run YOURUSERNAME/YOURREPO:ARBITRARYTAG
|
|
```
|
|
|
|
> Note: If you don't specify the `:ARBITRARYTAG` portion of these commands,
|
|
the tag of `:latest` will be assumed, both when you build and when you run
|
|
images.
|
|
|
|
You'll see this stranger of a machine pull your image, along with Python and all
|
|
the dependencies from `requirements.txt`, and run your code. It all travels
|
|
together in a neat little package, and the new machine didn't have to install
|
|
anything but Docker to run it.
|
|
|
|
## Recap and cheat sheet for images and containers
|
|
|
|
To recap: After calling `docker run`, you created and ran a container, based on
|
|
the image created when you called `docker build`. Images are defined in a
|
|
`Dockerfile`. A container is an instance of an image, and it has any package
|
|
installations, file writes, etc that happen after you call `docker run` and run
|
|
the app. And lastly, images are shared via a registry.
|
|
|
|
```shell
|
|
docker build -t friendlyname . #Create image using this directory's Dockerfile
|
|
docker run -p 4000:80 friendlyname #Run image "friendlyname" mapping port 4000 to 80
|
|
docker run -d -p 4000:80 friendlyname #Same thing, but in detached mode
|
|
docker ps #See a list of all running containers
|
|
docker stop <hash> #Gracefully stop the specified container
|
|
docker ps -a #See a list of all containers on this machine, even the ones not running
|
|
docker kill <hash> #Force shutdown of the specified container
|
|
docker rm <hash> #Remove the specified container from this machine
|
|
docker rm $(docker ps -a -q) #Remove all containers from this machine
|
|
docker images -a #Show all images that have been built or downloaded onto this machine
|
|
docker rmi <imagename> #Remove the specified image from this machine
|
|
docker rmi $(docker images -q) #Remove all images from this machine
|
|
docker login #Log in this CLI session using your Docker credentials (to Docker Hub by default)
|
|
docker tag <image> username/repository:tag #Tag <image> on your local machine for upload
|
|
docker push username/repository:tag #Upload tagged image to registry (Docker Hub by default)
|
|
docker run username/repository:tag #Run image from a registry (Docker Hub by default)
|
|
```
|
|
|
|
[On to "Getting Started, Part 3: Stateful, Multi-container Applications" >>](part3.md){: class="button darkblue-btn"}
|