An Intro to Docker for Djangonauts
CC Attribution - NoDerivatives 4.0 International:
You are free to use, copy, distribute and transmit the work or content in unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor.
Henschel, Lacey Williams
If you've never used it before, Docker can be a little overwhelming. There is new vocabulary to learn, new commands to memorize, and new files to add to each project. Most resources fall into one of two camps: they teach you the bare bones of Docker but still leave you with too many questions to comfortably try it on your own, or they throw you into the deep end by assuming you're more familiar with Docker than you are. In this talk, you will find that middle ground: a talk that doesn't assume you're familiar with Docker and so keeps everything simple, but leaves you with enough information that you can get started as soon as you leave the room. Together, we will step through the parts of a Dockerfile and learn what they do. Then, I'll introduce you to Docker Compose and explain why using it to run multiple containers is helpful. Finally, you will learn to run commands and execute scripts from the command line using Docker, how to enter a container and why you might need to, and what to do before you go home for the day. This talk is for Djangonauts who are curious about Docker, need to learn to use it for work or personal projects, or who seek a better understanding of containers in general. Audience members might be beginners to Python/Django or not, but they are definitely beginners to containerizing all the things. The audience should be familiar with basic Python concepts such as variables and running things from the command line. Some basic familiarity with Django would be helpful, as the sample project I will demo is a Django app, but beginner Djangonauts will be able to follow along. Introduction - Who am I? (1 minute) What is Docker? Why use it? (3 min) - Eliminates "it works on my machine" problems - Never install anything globally ever again - Great for working with teams Docker definitions (4 min) - Image - Container - Dockerfile - Where do virtual environments fit in? Dockerfile (5 min) - Go through parts: Python image, setting environment variables, dealing with requirements.txt, set working directory of /code/, expose port Docker Compose - What is Docker Compose? (3 min) Allows you to run more than one container in a Docker application Useful for running multiple services like your web server, database, Redis, etc. in separate containers as part of one Docker application docker-compose.yml (6 min) Version Define services (db and web for this presentation) Explain volumes Run commands from services, like runserver Define dependencies Docker on the Command Line (8 min) - docker-compose up - manage.py commands (docker-compose run web ./manage.py [command]) - running tests (docker-compose run web py.test [or ./runtests.py]) - opening a shell (docker-compose run web bash) - viewing containers (docker ps) and entering a container (see where the /code/ dir is that we created in the Dockerfile) - docker-compose down Q&A
 All right. I have my speaker notes today, which is wonderful. Good morning. We're moving from Daniele's wonderful, beautiful talk about art and naivete, and we're moving from those concepts to things that are very, very more practical. So I hope that this change of pace is comfortable for everyone. I'm going to try to make it a little bit whimsical too, so we still get to keep a little bit of that spirit that Daniele has left us with. Oh, I have to turn this on. There we go. So you've probably figured out by now that my name is Lacey, and I'm one of the organizers of DjangoCon US. I also work for a company called RevSys. We're a Django consultancy based in Kansas in the United States. I work remotely full-time. But yeah, so if you'd like to talk to me later today about Django or DjangoCon or the work that we do at RevSys, please seek me out. I won't be taking questions at the end of this talk, but I would really love to chat with you at the break or at lunch or some other time, really about anything that you'd like to talk to me about. But right now, we're going to talk about Docker. And Docker is a little bit confusing for people, and so I'm really happy that Daniele used some images from this same comic because I've used a couple too. But the punchline from this particular slide is that someone wants to create this side-by-side display, but this tablet that they're using doesn't have split-screen support. So they say, I downloaded the SDK and the IDE, registered as a developer, and started reading the language as docs. And then I realized it would be way easier to just get two smaller phones and glue them together, and on that day, I achieved software enlightenment. And then their friend says, but you never learned to write software, and the original person says, no, I just learned how to glue together stuff that I don't understand. And this is kind of how a lot of people feel about Docker. At its core, Docker feels a lot like magic. And whenever you take tutorials on Docker, a lot of those tutorials consist of, just copy this file and put it at the top of your project, and then run this command, and you're good to go. And that will get you through a Hello World app. But once you start actually using Docker, it doesn't leave you with enough information to feel confident debugging, to make the changes that you need to. Whenever you're just told to copy and paste a chunk of code, you aren't empowered with the information to do other things with that. So that's part of what we're going to do today is we're going to get very detailed and get kind of a micro level of what the things that you're copying and pasting in these tutorials are doing in the hope that that leaves you with the ability to go forth on your own and do more things with Docker. So one of the things that Docker does is it allows you to separate your dependencies from one another, especially in different projects, which is a similar thing that virtual environments do, but Docker handles it a little bit differently, and we'll talk about that. Separately or differently from a virtual environment, Docker shares your operating system. A virtual environment usually builds its own guest operating system on top of yours. Docker just shares yours and goes up from there, so it's a little bit lighter weight in that way. But one of the things I really, really love about Docker is that it puts everyone on the same page on your team. So whenever you're using a virtual environment, you still have to have your own system Python, and you and your teammates might have slightly different minor versions or even very different major versions, and that might work on some of your projects, but you'll run into bugs and you get into that situation where you're having a problem and your coworker pulls down your code and they run it and it works on their machine, you know, so you're not really left with very much with that. With Docker, you're defining everything down to the Python version very, very specifically, so all of your coworkers will be running things in the same environment, all of those will be identical, and it makes it a lot easier for you to help each other. And the other thing that I love is that you don't actually need to have a system Python installed at all with Docker. You can just download Docker, and Docker takes care of everything else, so you don't have to have anything on your machine anymore, which is really wonderful. But the thing that is a little bit tough about Docker is that it does have a pretty steep learning curve, and I know because it took me a while to feel really comfortable with it. I only started using Docker about six or seven months ago, and I said yesterday in my lightning talk that the best person to give a talk on a given topic is the person who was most recently confused by that topic, and that's me. That's the spirit in which I proposed this talk, is that I was frustrated and it took me a while to get my feet under me and feel like I knew what I was doing, and I'm hoping to save you from a little bit of that pain. I will say, though, that if you consider yourself a Docker expert, if you use Docker day in and day out, you might not learn anything new in this talk, but I hope that you maybe will have an idea about the things about Docker that are confusing, so you're able to help your coworkers and people later on. And so I did talk a little bit about virtual environments, and I'm not asking you to abandon virtual environments. I still like them. I have a system Python installed. I use virtual and wrapper. I really love that work on command. It's very, very convenient. But there's another XKCD comic that kind of shows the different paths that our Python environments can take, and the punchline for this one is my Python environment has become so degraded that my laptop has been declared a super fun site, and I've definitely been in that place before, too. So the way that we're going to talk about Docker today is by talking about Harry Potter, because that's a completely natural thing to do. To talk about the concepts of images and containers, which is a Docker concept, we'll be talking about Polyjuice Potion. And if you're not familiar with the Harry Potter books or with this specific potion, the way that Polyjuice Potion works is you brew this potion, and then you get someone else's hair, a hair of someone that you would like to impersonate, and you add it to that potion. Then you drink that potion, and you look like that person, and you get to impersonate them and get up to all kinds of hijinks. So in this situation, Docker is the potion. Docker is the vessel through which everything happens. And then the Docker image, the thing that you want to turn into is, sorry, Docker, the image, is the person that you want to turn into. So whenever you start a project in Docker, you start by defining your image. The image is, it defines the basic software of your project. The Docker docs say that it is the executable that contains all of the packages that your software needs. Docker also has this concept called a container. And the container is you. Whenever you're drinking this potion and you're running around looking like somebody else, you are running an image in a container. You're not actually that other person, you just look like them for a little bit. So Docker would say that the container is the runtime instance of the image. So like I said, you never actually run the image itself, you run a little copy of the image inside of a container. And this is a pretty important concept to understand. So I'm going to put it up here one more time. The image is the executable with all of the packages of your project. It gets your project ready to run. The container is the thing that actually runs. It's the runtime copy of your image that you've defined. But how do you define your image? So in Docker, there's this file that you add to the project level, the same level as your manage.py. And it's just called Dockerfile. It's Docker with a capital D. There is no file extension. And that's where you define all of the specifics about your project. So you can think about the Dockerfile as the DNA. That's that person's hair that you would like to turn into. That's the definition of your project. And in the Dockerfile, you specify a lot of different things. You specify the version of Python that you want to use. You can set environment variables. You can install specific requirements, things like that. And we'll get into more detail about that in just a moment. So again, I'm going to have these definitions up here just because these are concepts that are pretty important to know. So just to repeat, the image is the definition of your project. The container is what your project is actually running in based on your image. And the Dockerfile is the file that defines what your image is going to look like. And so this is where this analogy kind of comes into play. And so if you're familiar with Harry Potter, then you can kind of look at this a little bit. You use the hair to tell this particular potion how to change into somebody else. And then you drink it to actually turn into that potion. With Docker, you use the Dockerfile to tell Docker how to build your image. And then you run your project in a container. So that's kind of where this metaphor comes from. I'm going to take a quick moment here and grab some water. Okay. So this is the basic layout of a Dockerfile. You will see Dockerfiles that look more complicated than this. There are concepts called multi-stage builds that we won't get into. So your production Dockerfile, whenever you start using Docker, will probably look a little bit different than this. It will contain more lines. But this is all that you need to get started with Docker. This will get you up and going. And then you can explore the more complicated things. So we're going to go through this quickly line by line just to make sure that we understand this from statement from Python 3.6. You probably guessed correctly that this means that we want to use Python 3.6. That's the version that we want to use. And the reason that we get to do this is because Python publishes all of their versions as images on Docker. So just like whenever you're born, you inherit DNA from your parents. And just like in Python classes, we can inherit from one another, we can base images on top of other images. So we get to say in our Dockerfile, I want to grab everything that's in this other image that's published on the Docker hub, and I want to make use of that. This is an environment variable that basically says that you want to be able to see your output in the console. So if you want to see what you're doing, this is a handy line to have. Now we talked about containers, about how your project is going to be running in its own container. And what that means is that your container doesn't really have access to the code as it lives on your machine. So you have to tell the container, hey, my code is here, and I want you to have that. So in this line, we're taking the requirements file that's in our current directory, and we're telling Docker, I want you to copy that requirements file into a directory called code that is going to be in the container. Now at this point, the container doesn't actually have that directory. The copy statement will automatically create the directory if it doesn't already exist, and then copy. Now we want to actually install our requirements, because again, we're running in a container, we're not running in a virtual environment, so we have to tell the container, you know, these are called the image that whenever we run our container, we're going to want all of our dependencies, and after that we copy the rest of our code into that directory called code that lives on the container. And you don't necessarily have to do these things separately, like you could just copy everything into code, and then, you know, run pip install to install your requirements, but it's nice to separate that so that if you're getting an error, you get to isolate that a little bit more easily. Now whenever you're using Docker, you can still do things like run management commands, and I think that most of us are used to running things like.slashmanage.py shell or something like that, but now that we have all of our code in the container in this extra directory called code, we would have to type slash code slash manage.py, et cetera, and that's a little bit annoying, so we can get rid of that annoyance by setting our working directory as that code directory in the container. So whenever our container starts, it will automatically stick us in that directory, and then we can run commands the way that we're used to. We want to expose our port so that we're able to see that port 8,000 that we need whenever we're running the server, and then we can put our Python command right there in the Docker file, so we can say, hey, you know, I want to run my server whenever my image is created, whenever my container starts, I want my server to go ahead and start running for me. So now we have the DNA for our project, we have our Docker file, we're ready to build our image, so we have this file, but the file itself doesn't create the image. We have to tell Docker, hey, I want you to create this image now. So to brew that potion, we run this command, this Docker build dot, Docker build everything that's in this directory, and whenever you run that command, you'll see a lot of output. I have bolded the ones that are kind of important, but you run Docker build, and then you see that it's sending the build to a context, it's going through all of these steps, and then the very last line, if everything has gone well, will be successfully built, some kind of random name. And that name is the name of your image, and that doesn't really make a lot of sense, right? That's just a random string of letters and numbers that's not meaningful to us. So Docker provides us with a way to give our images a name so that we can keep track of them more easily. If you add that T flag, then you can tell Docker, hey, I want you to give this image a name so that I can access it more easily, and that way you don't have to tell Docker to show you all of your images, scroll through, find the right one, copy it, and then use that Docker ID. You can just use the name of your project or a name that's meaningful to you. And we can see this paradigm before, right? Like if we go back, we can see that it says successfully tagged Hogwarts colon latest. That's not the first time that we've actually seen that paradigm. When we created our Docker file, our very top line was from Python colon 3.6. That's because that's how Docker names things, right? So whenever you create an image, Docker automatically assigns that image a version. If you don't assign it a specific version, that version will just be called latest. It's the most recent version. For Python, Python has published all kinds of versions of its images. So you have the specific Python images for the different versions, but you can also download smaller Python images if you don't need all of Python. So you can make your image lighter weight. But the way that you'll see that is the image, a colon, and then the tag, and another word for tag is basically version. So now we get into the concept of layers. Docker is layered, which is a pretty important concept to grasp. And you can kind of get a sense of this layeredness whenever you ran that Docker build command. You saw that Docker was building step one out of eight, step two out of eight. Every line in your Docker file, every single line that has anything in it is its own layer in your image. So Docker builds those one at a time. So the very first time through, Docker builds each layer by hand. So it's going through, it's crafting everything, it just builds everything by hand. But the second time through, if that thing has changed, Docker knows it doesn't need to rebuild it from scratch, right? So it just goes ahead and uses a cache, it will run very quickly, and that works really well. But if you change something, Docker will use a cache until it gets to the thing that has changed, and then it can't use a cache anymore at all. So if you change something in the middle, it will use a cache until you get to that middle, and then everything after that gets rebuilt by hand again. And the reason that this matters is because that can impact some performance things, it can take longer. So it's just good to know that whenever you change something about your Docker file, you might run into this. And with that in mind, it's also good to, whenever you're creating your Docker file, to do things in an order that lets you put the things that are least likely to change at the top, and then things that you might be fiddling with more frequently at the bottom. So we can actually see this happen. I added a requirement to my requirements file. So I didn't actually change my Docker file, I changed the requirements file. But because I'm telling Docker, hey, I want you to copy my requirements file, Docker knows, oh, something about this requirements file changed. So at the top, we can see that in step two, it's using a cache. But then we get down to step four, where we're running our requirements, and we're downloading Django fresh. The last image that we used already had Django, it could have used a cache, but since I added a new library, maybe I added requests or something, it has to download Django from scratch all over again, and then keep going. So that's the Docker file. So just to recap, and you're probably going to get annoyed with me saying this, but I'll say it again. The image is the executable that contains all of the packages for your project. So it's basically the person that you want to turn into. The container is the runtime instance of your image. It's the thing that actually is running your project. So that's you once you've drunk the potion. And the Docker file is the DNA of your project. That's the definition that says, this is what I want my project to look like. This is what it needs. So now that we've had potions class, we're going to move on to charms. And so let's talk about the different commands that you can use to get Docker to give you some information about your project. So you can ask Docker, hey, what images do I have? And the way that you do this is with just the Docker images command. And Docker gives you some helpful information. So it gives you the repository that your image is in, the tag that that is, that it has, the image ID when it was created, and also the size of your image. And when you're using Docker in production, the size of that image gets pretty important. A few, I think, months ago now, Revs has published its own version of Python that is smaller than some of the others. And so it makes the images build a lot faster. But that's not something that you need to worry about for your first time out in Docker. But as you get further along in Docker, you'll probably want to explore some of the smaller images. You can also ask Docker to reveal your containers with the command Docker container ls. You can also use Docker PS. There's a cheat sheet at the end that I'll link to. You are not expected to memorize all of these. I certainly haven't. So, but you can see here that we, you know, where Docker is expecting to give us a container ID, the image that container is based on, commands it's running, and some other data, but nothing is there. And the reason that nothing is there is because so far we've built our image, but we haven't actually started running our containers. Containers don't exist until you ask them to. So let's ask our image to give us a container. And we do that with the Docker run command. You run Docker run dash P, which stands for publish, 8,000 colon 8,000, which stands for hey, take, you know, the containers, port 8,000, and publish it to my local machines, port 8,000, so that I can still use localhost in my browser the way that I'm used to. And then the name of the image that you want your container to be based on. Later on I'll show you an easier way to do this. But if you wanted to start just with Docker, this is how you would run a container. Specifically for a Django project. Not all containers will need the ports. For a Django project, you need that port. And we can prove to ourselves once our container is running, if we open up a new console tab and we ask Docker to tell us about our containers again, so then we get the container ID, the image, the commands that it's running, status, some other information about it, and then that name there, you see the names on the bottom right, that's kind of a nonsense name that Docker gives your container, I didn't name the container for event Alan, I don't know what it means, Docker puts two words together with an underscore, which is I think a little fun and whimsical. You can also hop into your container and poke around, you do that with the Docker exec command, and learning how to exec into your container can be a little bit painful whenever you start to Google it, people have different strategies, but this is the one that works pretty well for me. So Docker exec dash it, the ID of your container, which remember you get by running Docker container LS, so just copy that container ID and then paste it, and then shell, and you can actually see that we wind up in the code directory, right? So whenever we set that working directory in our Docker file, we can prove that Docker is doing that for us, so whenever we enter our container, we are immediately where we expect to be. And we can prove that to ourselves by asking the container to just using a regular bash command, asking it to list all of its files, so we see our manage.py file, our requirements file, and our Docker file the way that we expect to. We can also go up a level and then list the directories that are there, so we see our code directory is the second one, but we also see all of these other directories, and we don't really need to worry about those right now, you'll get to a point where you might need to worry about them, but for today we don't, it's good to know that they are there though. The container is doing its own thing, it has a bunch of its own things installed, in one of these directories you can actually go into it, go down a few levels, and see all of the packages that you've installed, in another one of these directories you can drill down into it and you can see where your database lives, so it's good to know that this is there, we won't go into it today, that's a little bit advanced charms for us. To get out of your container, you control D, the reason I have this slide is it took me forever to learn this. This is a keyboard shortcut that probably everyone in the room knew about me, but just in case there's two of you that didn't, there you go. And then we can see though, once we've hopped into the container and hopped out, we never asked our container to stop running, so it's still running, we can ask Docker to tell us the containers that exist, and just because we hopped into the container and hopped out, the container didn't change, it's still chugging along doing what we asked it to do. We can stop our container if we don't want it to be running for a little bit, if we want to change something or test something out, we can stop it with just Docker stop and the container ID. And if we're using Docker stop, we can bring it back pretty easily with Docker start, so it doesn't have to rebuild anything, it doesn't have to retrieve the image, the container is still there, we had basically just paused it, so stop really means pause. We can kill the container entirely. So if you run Docker kill, this means I want to blow this container away, I never want to see it again. Now remember, your image is still there, but once you've run Docker kill, you'll have to run the Docker run command to get your container back. If you kill your container and try to start it again, nothing will happen, because it's dead. Okay. Now advanced potions, Docker compose, I told you that I would show you an easier way to start your container. This is the command that we know about so far, right? Every run dash p, 8,000, the name of our image. And it's not like a huge command, but it's a little bit more complicated than manage.py run server, right? It's a lot to remember, you have to remember the port, the name of your image, and that's a little bit annoying. And so then we get to Docker compose, which is a tool that lets us do things a little bit more easily. It's free. When you download Docker, if you're on a Mac, Docker compose comes with Docker. If you don't have a Mac, then it is an extra download. But again, it's free, so that's fine. And the biggest thing that Docker compose gets you is the ability to run more than one container at once. So my favorite feature of compose is just that it's easier to type. But the second favorite feature is that I can identify several containers, which we'll talk about why that's useful in just a moment. And finally, I can relate those containers to each other. So I can say, you know, if I identify a database container, I can say, I don't want to start my web server until I have my database up. And that's exactly what we'll do. So you can think about Docker compose as that magical bag that Hermione has in the very last Harry Potter movie. The character Hermione has this bag whenever they're on the run from the law, and it's pretty small, but it holds essentially an infinite amount of stuff. So you'll see her reaching her arm into this tiny bag, and it's all the way up to her shoulder, and she's pulling out potions and books and a tent. All kinds of stuff comes out of this bag. And that's kind of what Docker compose is. It lets you put all of these containers, identify them, define them in this one file, and then you have access to them by just running one command. You don't have to run several containers individually. So Docker compose comes with its own file that also goes at the wherever you're managed.py is, wherever your Docker file is. It's written in YAML. If you've not written in YAML before, that syntax is pretty well documented, and it's a relatively straightforward syntax. It relies on that nesting that we like so much about Python. So we'll go through this one, too. The version is just a version of compose that you're using. Three is the most recent version right now. So the version that you're using will impact the syntax that compose is expecting from you. And then you identify your containers. So services is basically another word for containers in Docker compose parlance. So I'm identifying a DB, a database container, and also a web container. And this is also one of my other favorite features about Docker compose is you can do things like use Postgres without having to download Postgres on your machine. And the reason that this is wonderful is because two or three years ago, I tried to download Postgres and get it configured so that I could work on a particular open source project, and I couldn't get it to work. I didn't have the right permissions on my machine or something like that. I gave up. But now I can just say, hey, just like in my Docker file, I said I want to base this image on this particular version of Python. I can tell Docker compose, I want to base my database on this image of Postgres that's published. Now, I just have Postgres here, so it's going to automatically get the latest version. But you could add that colon and a specific tag to specify the version of Postgres that you want to use. And of course, whenever you're making changes about your database, you'll want to update your settings.py in the ways that you're used to tell Django to expect a different kind of database than the one that it ships with. In your web container, you have to do a little bit more. You have to tell Docker about a few more things so that it will work properly. You can run this command. I usually have mine set up to go ahead and run migrations before it starts my server. There are some pros and cons to that. You probably don't want to have compose make your migrations, but having compose go ahead and run them can save you a little bit of time. You also create this concept called a code volume. A volume is just a way for Docker to keep track of a particular chunk of data or files between your coding sessions. And then you can go ahead and publish those ports. This is the same concept as whenever we were using the Docker run command. For telling compose, I want to take the containers port 8,000 and publish it to my machine's port 8,000 so that I can access my website in my browser. And then finally, I told you before that we could relate containers to one another. That's what we're doing here. We're saying this web container depends on the database container. So I don't want my web container to start until my database container is up and running. And a few other things that you can do with compose is you can add services for, like your caching service, like Redis, or your task service, like Celery. And if you have, for example, a Celery task that is supposed to run every five minutes and it has a lot of console output, that can get really, really annoying. It kind of clogs up your console. So one of the really nice things that you can do is you can start your project and then you can stop just that Celery task, like that Celery service. So your Celery task just won't run if you're working on something else, which is a little bit handy. I like that. It saves you from having to change your Celery beat settings in your settings file or something, which you might forget to unchange later on. And what this means, since you noticed here that we have that command line where we're going ahead and running our server, we don't actually need to do that from our Docker file anymore. So we can just get rid of that. And now, instead of having to run Docker run-p 8000, 8000 Hogwarts, we can just run Docker compose up. And if Docker has never seen this image before, it's never built it before. Docker compose up knows to build that image before it starts to run a container. If you need to rebuild your image, you need to make sure that your image gets rebuilt, you can just add the build flag. And whenever you run Docker compose up, if you don't already have the image, you'll see all that output where it's creating the image. But if you already have your image, you'll see some output that looks something like this. So you'll see that we're creating a DB service, and we're also creating a web service, and we're seeing that we're attaching that database service to that web service. So compose is doing what we told it to. It's creating the database service first, then it's creating the web service, and then it's relating them together. And then we can see that it's running the migrations just like we asked it to, and then it's starting our server. And if we go to local host in our browser, we will have access to our website the way that we're used to. And if we open a new tab, and we run that Docker container ls command again, we can actually see that we are definitely running more than one container now. So we see that web container like we expect. But we also see this new Postgres container that contains our database. And then you can do things like run management commands using Docker compose. So you just prefix everything with Docker compose, run. And instead of having to have the container ID, like you have to go and copy those weird strings that don't make any sense, you can just specify the name of the service that you defined and compose. And then after that, it's dot slash manage dot pie, whatever the way that you're used to. And you probably notice this dash dash rm flag. Whenever you run a Docker compose run command, that will create a new container that it can run that particular command in. But you don't actually need that container once it's finished doing the task that you ask it to do. So you can tell Docker, hey, run, make migrations in a new container, but then just trash that container because I won't need it anymore. And kind of like magic, Docker will go ahead and, I saw my time warning and then I lost my train of thought. So Docker will go ahead and tell your first web container about the changes that were made in the second container, which is pretty handy as well. You can hop into the shell in the same way. Katie taught us about the shell yesterday. With the same technique, Docker compose run, web manage dot pie shell, and then you have access to the shell just the way that you're used to. And again, you can see that you have that database container that's getting created and being confirmed before you hop into the shell so that whenever you're trying to access the data in your models, you get those pretty easily. And then you can stop and start containers pretty similarly to the way that you do that with vanilla Docker. You just use Docker compose instead of Docker. And again, instead of having to get that container ID, that nonsense string, you just use the name of your service. So you can start and stop your web containers, which is pretty handy because my experience is that sometimes whenever you make changes in a template, Django kind of likes you to stop and restart the server, which gets a little bit annoying, but it happens sometimes. And so this is a simpler way than having to copy and paste that container ID to get that to happen. At the end of the day, when you're ready to go to sleep, you run Docker compose down. Docker will very gracefully remove your containers and shut down its system so that you're ready to go home. And I've created a little sample project for you on GitHub that you should be able to check out, download Docker, download Docker compose, and just run. There are some instructions there. And of course, feel free to ask me any questions. I put it together last night at about one in the morning. So if there are any errors, please let me know when I would love to correct those. I was not at my freshest at that point. And so I want to close with a quote from Albus Dumbledore. Albus Dumbledore, the greatest headmaster that Hogwarts has ever known, said, it is the unknown we fear when we look upon Docker and nothing more. And what he meant was that Docker can be a little bit intimidating. There's a lot to learn. There's new vocabulary. There's new commands. But at the end of the day, it's a really wonderful tool to use. I am a complete convert to it. It was pretty intimidating to me at first. But now I'm very much in love with Docker. And it has so many more capabilities than what I've gone into here. And I've given you just many, many resources. And again, I will put my slides up. These will be clickable, so you will know. But there are so many resources available to you. Some are better than others. But that Docker cheat sheet that's like third from the bottom, that is just a lifesaver. It has the full list of Docker commands and a pretty easy to digest format. And I think I've used just about all of my time. So that's all I have for today. Thank you. Thank you. Thank you. Thank you. Thank you. Thank you.
 All right.  I have my speaker notes today, which is wonderful.  Good morning.  We're moving from Daniele's wonderful, beautiful talk about art and naivete, and we're moving  from those concepts to things that are very, very more practical.  So I hope that this change of pace is comfortable for everyone.  I'm going to try to make it a little bit whimsical too, so we still get to keep a little bit  of that spirit that Daniele has left us with.  Oh, I have to turn this on.  There we go.  So you've probably figured out by now that my name is Lacey, and I'm one of the organizers  of DjangoCon US.  I also work for a company called RevSys.  We're a Django consultancy based in Kansas in the United States.  I work remotely full-time.  But yeah, so if you'd like to talk to me later today about Django or DjangoCon or the work  that we do at RevSys, please seek me out.  I won't be taking questions at the end of this talk, but I would really love to chat  with you at the break or at lunch or some other time, really about anything that you'd  like to talk to me about.  But right now, we're going to talk about Docker.  And Docker is a little bit confusing for people, and so I'm really happy that Daniele  used some images from this same comic because I've used a couple too.  But the punchline from this particular slide is that someone wants to create this side-by-side  display, but this tablet that they're using doesn't have split-screen support.  So they say, I downloaded the SDK and the IDE, registered as a developer, and started  reading the language as docs.  And then I realized it would be way easier to just get two smaller phones and glue them  together, and on that day, I achieved software enlightenment.  And then their friend says, but you never learned to write software, and the original  person says, no, I just learned how to glue together stuff that I don't understand.  And this is kind of how a lot of people feel about Docker.  At its core, Docker feels a lot like magic.  And whenever you take tutorials on Docker, a lot of those tutorials consist of, just  copy this file and put it at the top of your project, and then run this command, and you're  good to go.  And that will get you through a Hello World app.  But once you start actually using Docker, it doesn't leave you with enough information  to feel confident debugging, to make the changes that you need to.  Whenever you're just told to copy and paste a chunk of code, you aren't empowered with  the information to do other things with that.  So that's part of what we're going to do today is we're going to get very detailed and get  kind of a micro level of what the things that you're copying and pasting in these tutorials  are doing in the hope that that leaves you with the ability to go forth on your own and  do more things with Docker.  So one of the things that Docker does is it allows you to separate your dependencies from  one another, especially in different projects, which is a similar thing that virtual environments  do, but Docker handles it a little bit differently, and we'll talk about that.  Separately or differently from a virtual environment, Docker shares your operating system.  A virtual environment usually builds its own guest operating system on top of yours.  Docker just shares yours and goes up from there, so it's a little bit lighter weight  in that way.  But one of the things I really, really love about Docker is that it puts everyone on the  same page on your team.  So whenever you're using a virtual environment, you still have to have your own system Python,  and you and your teammates might have slightly different minor versions or even very different  major versions, and that might work on some of your projects, but you'll run into bugs  and you get into that situation where you're having a problem and your coworker pulls down  your code and they run it and it works on their machine, you know, so you're not really  left with very much with that.  With Docker, you're defining everything down to the Python version very, very specifically,  so all of your coworkers will be running things in the same environment, all of those will  be identical, and it makes it a lot easier for you to help each other.  And the other thing that I love is that you don't actually need to have a system Python  installed at all with Docker.  You can just download Docker, and Docker takes care of everything else, so you don't have  to have anything on your machine anymore, which is really wonderful.  But the thing that is a little bit tough about Docker is that it does have a pretty steep  learning curve, and I know because it took me a while to feel really comfortable with  it.  I only started using Docker about six or seven months ago, and I said yesterday in my lightning  talk that the best person to give a talk on a given topic is the person who was most recently  confused by that topic, and that's me.  That's the spirit in which I proposed this talk, is that I was frustrated and it took  me a while to get my feet under me and feel like I knew what I was doing, and I'm hoping  to save you from a little bit of that pain.  I will say, though, that if you consider yourself a Docker expert, if you use Docker day in and  day out, you might not learn anything new in this talk, but I hope that you maybe will  have an idea about the things about Docker that are confusing, so you're able to help  your coworkers and people later on.  And so I did talk a little bit about virtual environments, and I'm not asking you to abandon  virtual environments.  I still like them.  I have a system Python installed.  I use virtual and wrapper.  I really love that work on command.  It's very, very convenient.  But there's another XKCD comic that kind of shows the different paths that our Python  environments can take, and the punchline for this one is my Python environment has become  so degraded that my laptop has been declared a super fun site, and I've definitely been  in that place before, too.  So the way that we're going to talk about Docker today is by talking about Harry Potter,  because that's a completely natural thing to do.  To talk about the concepts of images and containers, which is a Docker concept, we'll  be talking about Polyjuice Potion.  And if you're not familiar with the Harry Potter books or with this specific potion,  the way that Polyjuice Potion works is you brew this potion, and then you get someone  else's hair, a hair of someone that you would like to impersonate, and you add it to that  potion.  Then you drink that potion, and you look like that person, and you get to impersonate them  and get up to all kinds of hijinks.  So in this situation, Docker is the potion.  Docker is the vessel through which everything happens.  And then the Docker image, the thing that you want to turn into is, sorry, Docker, the  image, is the person that you want to turn into.  So whenever you start a project in Docker, you start by defining your image.  The image is, it defines the basic software of your project.  The Docker docs say that it is the executable that contains all of the packages that your  software needs.  Docker also has this concept called a container.  And the container is you.  Whenever you're drinking this potion and you're running around looking like somebody else,  you are running an image in a container.  You're not actually that other person, you just look like them for a little bit.  So Docker would say that the container is the runtime instance of the image.  So like I said, you never actually run the image itself, you run a little copy of the  image inside of a container.  And this is a pretty important concept to understand.  So I'm going to put it up here one more time.  The image is the executable with all of the packages of your project.  It gets your project ready to run.  The container is the thing that actually runs.  It's the runtime copy of your image that you've defined.  But how do you define your image?  So in Docker, there's this file that you add to the project level, the same level as your  manage.py.  And it's just called Dockerfile.  It's Docker with a capital D. There is no file extension.  And that's where you define all of the specifics about your project.  So you can think about the Dockerfile as the DNA.  That's that person's hair that you would like to turn into.  That's the definition of your project.  And in the Dockerfile, you specify a lot of different things.  You specify the version of Python that you want to use.  You can set environment variables.  You can install specific requirements, things like that.  And we'll get into more detail about that in just a moment.  So again, I'm going to have these definitions up here just because these are concepts that  are pretty important to know.  So just to repeat, the image is the definition of your project.  The container is what your project is actually running in based on your image.  And the Dockerfile is the file that defines what your image is going to look like.  And so this is where this analogy kind of comes into play.  And so if you're familiar with Harry Potter, then you can kind of look at this a little  bit.  You use the hair to tell this particular potion how to change into somebody else.  And then you drink it to actually turn into that potion.  With Docker, you use the Dockerfile to tell Docker how to build your image.  And then you run your project in a container.  So that's kind of where this metaphor comes from.  I'm going to take a quick moment here and grab some water.  Okay.  So this is the basic layout of a Dockerfile.  You will see Dockerfiles that look more complicated than this.  There are concepts called multi-stage builds that we won't get into.  So your production Dockerfile, whenever you start using Docker, will probably look a little  bit different than this.  It will contain more lines.  But this is all that you need to get started with Docker.  This will get you up and going.  And then you can explore the more complicated things.  So we're going to go through this quickly line by line just to make sure that we understand  this from statement from Python 3.6.  You probably guessed correctly that this means that we want to use Python 3.6.  That's the version that we want to use.  And the reason that we get to do this is because Python publishes all of their versions as  images on Docker.  So just like whenever you're born, you inherit DNA from your parents.  And just like in Python classes, we can inherit from one another, we can base images on top  of other images.  So we get to say in our Dockerfile, I want to grab everything that's in this other image  that's published on the Docker hub, and I want to make use of that.  This is an environment variable that basically says that you want to be able to see your  output in the console.  So if you want to see what you're doing, this is a handy line to have.  Now we talked about containers, about how your project is going to be running in its  own container.  And what that means is that your container doesn't really have access to the code as  it lives on your machine.  So you have to tell the container, hey, my code is here, and I want you to have that.  So in this line, we're taking the requirements file that's in our current directory, and  we're telling Docker, I want you to copy that requirements file into a directory called  code that is going to be in the container.  Now at this point, the container doesn't actually have that directory.  The copy statement will automatically create the directory if it doesn't already exist,  and then copy.  Now we want to actually install our requirements, because again, we're running in a container,  we're not running in a virtual environment, so we have to tell the container, you know,  these are called the image that whenever we run our container, we're going to want all  of our dependencies, and after that we copy the rest of our code into that directory called  code that lives on the container.  And you don't necessarily have to do these things separately, like you could just copy  everything into code, and then, you know, run pip install to install your requirements,  but it's nice to separate that so that if you're getting an error, you get to isolate  that a little bit more easily.  Now whenever you're using Docker, you can still do things like run management commands,  and I think that most of us are used to running things like.slashmanage.py shell or something  like that, but now that we have all of our code in the container in this extra directory  called code, we would have to type slash code slash manage.py, et cetera, and that's a little  bit annoying, so we can get rid of that annoyance by setting our working directory as that code  directory in the container.  So whenever our container starts, it will automatically stick us in that directory, and then we can  run commands the way that we're used to.  We want to expose our port so that we're able to see that port 8,000 that we need whenever  we're running the server, and then we can put our Python command right there in the  Docker file, so we can say, hey, you know, I want to run my server whenever my image  is created, whenever my container starts, I want my server to go ahead and start running  for me.  So now we have the DNA for our project, we have our Docker file, we're ready to build  our image, so we have this file, but the file itself doesn't create the image.  We have to tell Docker, hey, I want you to create this image now.  So to brew that potion, we run this command, this Docker build dot, Docker build everything  that's in this directory, and whenever you run that command, you'll see a lot of output.  I have bolded the ones that are kind of important, but you run Docker build, and then you see  that it's sending the build to a context, it's going through all of these steps, and  then the very last line, if everything has gone well, will be successfully built, some  kind of random name.  And that name is the name of your image, and that doesn't really make a lot of sense, right?  That's just a random string of letters and numbers that's not meaningful to us.  So Docker provides us with a way to give our images a name so that we can keep track of  them more easily.  If you add that T flag, then you can tell Docker, hey, I want you to give this image  a name so that I can access it more easily, and that way you don't have to tell Docker  to show you all of your images, scroll through, find the right one, copy it, and then use  that Docker ID.  You can just use the name of your project or a name that's meaningful to you.  And we can see this paradigm before, right?  Like if we go back, we can see that it says successfully tagged Hogwarts colon latest.  That's not the first time that we've actually seen that paradigm.  When we created our Docker file, our very top line was from Python colon 3.6.  That's because that's how Docker names things, right?  So whenever you create an image, Docker automatically assigns that image a version.  If you don't assign it a specific version, that version will just be called latest.  It's the most recent version.  For Python, Python has published all kinds of versions of its images.  So you have the specific Python images for the different versions, but you can also download  smaller Python images if you don't need all of Python.  So you can make your image lighter weight.  But the way that you'll see that is the image, a colon, and then the tag, and another word  for tag is basically version.  So now we get into the concept of layers.  Docker is layered, which is a pretty important concept to grasp.  And you can kind of get a sense of this layeredness whenever you ran that Docker build command.  You saw that Docker was building step one out of eight, step two out of eight.  Every line in your Docker file, every single line that has anything in it is its own layer  in your image.  So Docker builds those one at a time.  So the very first time through, Docker builds each layer by hand.  So it's going through, it's crafting everything, it just builds everything by hand.  But the second time through, if that thing has changed, Docker knows it doesn't need to  rebuild it from scratch, right?  So it just goes ahead and uses a cache, it will run very quickly, and that works really  well.  But if you change something, Docker will use a cache until it gets to the thing that has  changed, and then it can't use a cache anymore at all.  So if you change something in the middle, it will use a cache until you get to that middle,  and then everything after that gets rebuilt by hand again.  And the reason that this matters is because that can impact some performance things, it  can take longer.  So it's just good to know that whenever you change something about your Docker file, you  might run into this.  And with that in mind, it's also good to, whenever you're creating your Docker file, to do things  in an order that lets you put the things that are least likely to change at the top, and  then things that you might be fiddling with more frequently at the bottom.  So we can actually see this happen.  I added a requirement to my requirements file.  So I didn't actually change my Docker file, I changed the requirements file.  But because I'm telling Docker, hey, I want you to copy my requirements file, Docker knows,  oh, something about this requirements file changed.  So at the top, we can see that in step two, it's using a cache.  But then we get down to step four, where we're running our requirements, and we're downloading  Django fresh.  The last image that we used already had Django, it could have used a cache, but since I added  a new library, maybe I added requests or something, it has to download Django from scratch all  over again, and then keep going.  So that's the Docker file.  So just to recap, and you're probably going to get annoyed with me saying this, but I'll  say it again.  The image is the executable that contains all of the packages for your project.  So it's basically the person that you want to turn into.  The container is the runtime instance of your image.  It's the thing that actually is running your project.  So that's you once you've drunk the potion.  And the Docker file is the DNA of your project.  That's the definition that says, this is what I want my project to look like.  This is what it needs.  So now that we've had potions class, we're going to move on to charms.  And so let's talk about the different commands that you can use to get Docker to give you  some information about your project.  So you can ask Docker, hey, what images do I have?  And the way that you do this is with just the Docker images command.  And Docker gives you some helpful information.  So it gives you the repository that your image is in, the tag that that is, that it has,  the image ID when it was created, and also the size of your image.  And when you're using Docker in production, the size of that image gets pretty important.  A few, I think, months ago now, Revs has published its own version of Python that is smaller  than some of the others.  And so it makes the images build a lot faster.  But that's not something that you need to worry about for your first time out in Docker.  But as you get further along in Docker, you'll probably want to explore some of the smaller  images.  You can also ask Docker to reveal your containers with the command Docker container ls.  You can also use Docker PS.  There's a cheat sheet at the end that I'll link to.  You are not expected to memorize all of these.  I certainly haven't.  So, but you can see here that we, you know, where Docker is expecting to give us a container  ID, the image that container is based on, commands it's running, and some other data,  but nothing is there.  And the reason that nothing is there is because so far we've built our image, but we haven't  actually started running our containers.  Containers don't exist until you ask them to.  So let's ask our image to give us a container.  And we do that with the Docker run command.  You run Docker run dash P, which stands for publish, 8,000 colon 8,000, which stands for  hey, take, you know, the containers, port 8,000, and publish it to my local machines,  port 8,000, so that I can still use localhost in my browser the way that I'm used to.  And then the name of the image that you want your container to be based on.  Later on I'll show you an easier way to do this.  But if you wanted to start just with Docker, this is how you would run a container.  Specifically for a Django project.  Not all containers will need the ports.  For a Django project, you need that port.  And we can prove to ourselves once our container is running, if we open up a new console tab  and we ask Docker to tell us about our containers again, so then we get the container ID, the  image, the commands that it's running, status, some other information about it, and then  that name there, you see the names on the bottom right, that's kind of a nonsense name  that Docker gives your container, I didn't name the container for event Alan, I don't  know what it means, Docker puts two words together with an underscore, which is I think  a little fun and whimsical.  You can also hop into your container and poke around, you do that with the Docker exec command,  and learning how to exec into your container can be a little bit painful whenever you start  to Google it, people have different strategies, but this is the one that works pretty well  for me.  So Docker exec dash it, the ID of your container, which remember you get by running Docker container  LS, so just copy that container ID and then paste it, and then shell, and you can actually  see that we wind up in the code directory, right?  So whenever we set that working directory in our Docker file, we can prove that Docker  is doing that for us, so whenever we enter our container, we are immediately where we  expect to be.  And we can prove that to ourselves by asking the container to just using a regular bash  command, asking it to list all of its files, so we see our manage.py file, our requirements  file, and our Docker file the way that we expect to.  We can also go up a level and then list the directories that are there, so we see our  code directory is the second one, but we also see all of these other directories, and we  don't really need to worry about those right now, you'll get to a point where you might  need to worry about them, but for today we don't, it's good to know that they are there  though.  The container is doing its own thing, it has a bunch of its own things installed, in one  of these directories you can actually go into it, go down a few levels, and see all of the  packages that you've installed, in another one of these directories you can drill down  into it and you can see where your database lives, so it's good to know that this is there,  we won't go into it today, that's a little bit advanced charms for us.  To get out of your container, you control D, the reason I have this slide is it took  me forever to learn this.  This is a keyboard shortcut that probably everyone in the room knew about me, but just in case  there's two of you that didn't, there you go.  And then we can see though, once we've hopped into the container and hopped out, we never  asked our container to stop running, so it's still running, we can ask Docker to tell us  the containers that exist, and just because we hopped into the container and hopped out,  the container didn't change, it's still chugging along doing what we asked it to do.  We can stop our container if we don't want it to be running for a little bit, if we want  to change something or test something out, we can stop it with just Docker stop and the  container ID.  And if we're using Docker stop, we can bring it back pretty easily with Docker start, so  it doesn't have to rebuild anything, it doesn't have to retrieve the image, the container is  still there, we had basically just paused it, so stop really means pause.  We can kill the container entirely.  So if you run Docker kill, this means I want to blow this container away, I never want  to see it again.  Now remember, your image is still there, but once you've run Docker kill, you'll have to  run the Docker run command to get your container back.  If you kill your container and try to start it again, nothing will happen, because it's  dead.  Okay.  Now advanced potions, Docker compose, I told you that I would show you an easier way to  start your container.  This is the command that we know about so far, right?  Every run dash p, 8,000, the name of our image.  And it's not like a huge command, but it's a little bit more complicated than manage.py  run server, right?  It's a lot to remember, you have to remember the port, the name of your image, and that's  a little bit annoying.  And so then we get to Docker compose, which is a tool that lets us do things a little  bit more easily.  It's free.  When you download Docker, if you're on a Mac, Docker compose comes with Docker.  If you don't have a Mac, then it is an extra download.  But again, it's free, so that's fine.  And the biggest thing that Docker compose gets you is the ability to run more than one container  at once.  So my favorite feature of compose is just that it's easier to type.  But the second favorite feature is that I can identify several containers, which we'll  talk about why that's useful in just a moment.  And finally, I can relate those containers to each other.  So I can say, you know, if I identify a database container, I can say, I don't want to start  my web server until I have my database up.  And that's exactly what we'll do.  So you can think about Docker compose as that magical bag that Hermione has in the very  last Harry Potter movie.  The character Hermione has this bag whenever they're on the run from the law, and it's  pretty small, but it holds essentially an infinite amount of stuff.  So you'll see her reaching her arm into this tiny bag, and it's all the way up to her shoulder,  and she's pulling out potions and books and a tent.  All kinds of stuff comes out of this bag.  And that's kind of what Docker compose is.  It lets you put all of these containers, identify them, define them in this one file, and then  you have access to them by just running one command.  You don't have to run several containers individually.  So Docker compose comes with its own file that also goes at the wherever you're managed .py is, wherever your Docker file is.  It's written in YAML.  If you've not written in YAML before, that syntax is pretty well documented, and it's  a relatively straightforward syntax.  It relies on that nesting that we like so much about Python.  So we'll go through this one, too.  The version is just a version of compose that you're using.  Three is the most recent version right now.  So the version that you're using will impact the syntax that compose is expecting from  you.  And then you identify your containers.  So services is basically another word for containers in Docker compose parlance.  So I'm identifying a DB, a database container, and also a web container.  And this is also one of my other favorite features about Docker compose is you can do  things like use Postgres without having to download Postgres on your machine.  And the reason that this is wonderful is because two or three years ago, I tried to download  Postgres and get it configured so that I could work on a particular open source project,  and I couldn't get it to work.  I didn't have the right permissions on my machine or something like that.  I gave up.  But now I can just say, hey, just like in my Docker file, I said I want to base this image  on this particular version of Python.  I can tell Docker compose, I want to base my database on this image of Postgres that's  published.  Now, I just have Postgres here, so it's going to automatically get the latest version.  But you could add that colon and a specific tag to specify the version of Postgres that  you want to use.  And of course, whenever you're making changes about your database, you'll want to update  your settings.py in the ways that you're used to tell Django to expect a different kind  of database than the one that it ships with.  In your web container, you have to do a little bit more.  You have to tell Docker about a few more things so that it will work properly.  You can run this command.  I usually have mine set up to go ahead and run migrations before it starts my server.  There are some pros and cons to that.  You probably don't want to have compose make your migrations, but having compose go ahead  and run them can save you a little bit of time.  You also create this concept called a code volume.  A volume is just a way for Docker to keep track of a particular chunk of data or files  between your coding sessions.  And then you can go ahead and publish those ports.  This is the same concept as whenever we were using the Docker run command.  For telling compose, I want to take the containers port 8,000 and publish it to my machine's  port 8,000 so that I can access my website in my browser.  And then finally, I told you before that we could relate containers to one another.  That's what we're doing here.  We're saying this web container depends on the database container.  So I don't want my web container to start until my database container is up and running.  And a few other things that you can do with compose is you can add services for, like  your caching service, like Redis, or your task service, like Celery.  And if you have, for example, a Celery task that is supposed to run every five minutes  and it has a lot of console output, that can get really, really annoying.  It kind of clogs up your console.  So one of the really nice things that you can do is you can start your project and then  you can stop just that Celery task, like that Celery service.  So your Celery task just won't run if you're working on something else, which is a little  bit handy.  I like that.  It saves you from having to change your Celery beat settings in your settings file or something,  which you might forget to unchange later on.  And what this means, since you noticed here that we have that command line where we're  going ahead and running our server, we don't actually need to do that from our Docker file  anymore.  So we can just get rid of that.  And now, instead of having to run Docker run-p 8000, 8000 Hogwarts, we can just run Docker  compose up.  And if Docker has never seen this image before, it's never built it before.  Docker compose up knows to build that image before it starts to run a container.  If you need to rebuild your image, you need to make sure that your image gets rebuilt,  you can just add the build flag.  And whenever you run Docker compose up, if you don't already have the image, you'll see  all that output where it's creating the image.  But if you already have your image, you'll see some output that looks something like  this.  So you'll see that we're creating a DB service, and we're also creating a web service, and  we're seeing that we're attaching that database service to that web service.  So compose is doing what we told it to.  It's creating the database service first, then it's creating the web service, and then  it's relating them together.  And then we can see that it's running the migrations just like we asked it to, and then it's starting  our server.  And if we go to local host in our browser, we will have access to our website the way  that we're used to.  And if we open a new tab, and we run that Docker container ls command again, we can  actually see that we are definitely running more than one container now.  So we see that web container like we expect.  But we also see this new Postgres container that contains our database.  And then you can do things like run management commands using Docker compose.  So you just prefix everything with Docker compose, run.  And instead of having to have the container ID, like you have to go and copy those weird  strings that don't make any sense, you can just specify the name of the service that  you defined and compose.  And then after that, it's dot slash manage dot pie, whatever the way that you're used  to.  And you probably notice this dash dash rm flag.  Whenever you run a Docker compose run command, that will create a new container that it can  run that particular command in.  But you don't actually need that container once it's finished doing the task that you  ask it to do.  So you can tell Docker, hey, run, make migrations in a new container, but then just trash that  container because I won't need it anymore.  And kind of like magic, Docker will go ahead and, I saw my time warning and then I lost  my train of thought.  So Docker will go ahead and tell your first web container about the changes that were  made in the second container, which is pretty handy as well.  You can hop into the shell in the same way.  Katie taught us about the shell yesterday.  With the same technique, Docker compose run, web manage dot pie shell, and then you have  access to the shell just the way that you're used to.  And again, you can see that you have that database container that's getting created  and being confirmed before you hop into the shell so that whenever you're trying to access  the data in your models, you get those pretty easily.  And then you can stop and start containers pretty similarly to the way that you do that  with vanilla Docker.  You just use Docker compose instead of Docker.  And again, instead of having to get that container ID, that nonsense string, you just  use the name of your service.  So you can start and stop your web containers, which is pretty handy because my experience  is that sometimes whenever you make changes in a template, Django kind of likes you to  stop and restart the server, which gets a little bit annoying, but it happens sometimes.  And so this is a simpler way than having to copy and paste that container ID to get that  to happen.  At the end of the day, when you're ready to go to sleep, you run Docker compose down.  Docker will very gracefully remove your containers and shut down its system so that you're ready  to go home.  And I've created a little sample project for you on GitHub that you should be able to check  out, download Docker, download Docker compose, and just run.  There are some instructions there.  And of course, feel free to ask me any questions.  I put it together last night at about one in the morning.  So if there are any errors, please let me know when I would love to correct those.  I was not at my freshest at that point.  And so I want to close with a quote from Albus Dumbledore.  Albus Dumbledore, the greatest headmaster that Hogwarts has ever known, said, it is  the unknown we fear when we look upon Docker and nothing more.  And what he meant was that Docker can be a little bit intimidating.  There's a lot to learn.  There's new vocabulary.  There's new commands.  But at the end of the day, it's a really wonderful tool to use.  I am a complete convert to it.  It was pretty intimidating to me at first.  But now I'm very much in love with Docker.  And it has so many more capabilities than what I've gone into here.  And I've given you just many, many resources.  And again, I will put my slides up.  These will be clickable, so you will know.  But there are so many resources available to you.  Some are better than others.  But that Docker cheat sheet that's like third from the bottom, that is just a lifesaver.  It has the full list of Docker commands and a pretty easy to digest format.  And I think I've used just about all of my time.  So that's all I have for today.  Thank you.  Thank you.  Thank you.  Thank you.  Thank you.  Thank you.
DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_0_59.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_60_191.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_192_287.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_288_1199.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_1200_3143.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_3144_5819.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_5820_5891.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_5892_6323.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_6324_6743.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_6744_10115.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_10116_10595.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_10596_10751.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_10752_11315.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_11316_14219.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_14220_14591.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_14592_16811.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_16812_16919.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_16920_17387.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_17388_17627.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_17628_20279.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_20280_20627.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_20628_22955.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_22956_23039.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_23040_23951.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_23952_24035.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_24036_24263.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_24264_26375.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_26376_26579.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_26580_28439.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_28440_28667.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_28668_28715.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_28716_28763.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_28764_31163.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_31164_31211.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_31212_33179.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_33180_34679.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_34680_34979.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_34980_35507.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_35508_35591.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_35592_36959.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_36960_37127.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_37128_38339.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_38340_38387.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_38388_38519.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_38520_43691.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_43692_44747.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_44748_44867.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_44868_44915.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_44916_45815.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_45816_46775.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_46776_46955.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_46956_47015.png DjangoCon_2018_-_An_Intro_to_Docker_for_Djangonauts_47016_50950.png