Can we deploy yet?
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Tymoshchuk, Anastasiia
What happens when your features are done, your MVP is ready and you want to deploy your first production build? What do you do then? How do you make your first production build instead of re-using your development one? is your code ready to handle real user interactions? This talk will show a production ready checklist for your Python code; what to look for when creating a production-ready Docker image; what are the differences between development and production environments and builds. You will see how to deal with exceptions, logs, and metrics with real-world use cases.
 People from Germany might know you from the that you're the organizer of the Pi Berlin meetups and that you've been around PyConDE as well a lot. And of course we've seen you at other Europe items as well. Today you want to talk about how to make your code production ready. Yes, exactly. It's a super interesting topic because when you do stuff in production like now things can break all the time. So how to make sure that that works is interesting for us. Are you ready to do your screen share? Yes. Then I hope to have a nice talk. So actually I'm going to talk about production ready code and whether our code is ready to be deployed somewhere or not. A few words about myself. Thank you for a nice introduction. I'm basically connected from Berlin and I'm a Pi Berlin organizer since already a year and a half. I have 10 years in software development and seven years in Python. Actually there is a very interesting story which I will quickly share before we start. I'm here in Python because of the community most of the time on the conferences and I'm super happy that once our customer, I was a former C++ developer and once customers said that now you are a Python developer, you have to learn Python. I didn't understand how great was it because back then I was in Ukraine and there was not much of a community thing in Python at all. There was C++, Java and different meetups but not Python at all. And then when I moved to Berlin, I realized that there is so much to learn and to share with the community. Everyone is so supportive. So I'm super happy that I'm here right now and thank you for all the support. I know it's remote. I don't see anyone here right now but I can feel the support. Thank you so much. You're all invited to the next Pi Berlin meetup and if you're willing to speak for us, just feel free to drop by just a message or maybe just join the Pi Berlin channel. So what exactly does production mean to you? What do you mean by production ready code? What's the difference of production ready and non-production ready code? Actually, I read the book, release it. It's great and there is a favorite quote of this book is, you work so hard on your project. It looks like all the features are actually complete and most even have tests. You can breathe a sigh of relief, you're done or are you? There is no checklist what to go through on your code if it's ready for production or not. Can you deploy already to your customers? So I prepared some checklists for you. And before starting that, I would like to emphasize that the only difference between production and not production code is that there is a customer behind the computer or behind the device who is trying to run your code. The difference is that we are developers. We know how our code is supposed to work. We know where to click. We know what to do on the website or the application or whatever we are building. But the customer doesn't know. The only difference that code could break and it will break, we have to make sure that we know how to investigate, how to find the problem and fix it, how to provide a solution to our customers. And here are the checkpoints what to check before going to production. So first of all, I would talk about exception handling. There was also a very good talk yesterday about exception handling and I will also share some tips about that. And then how actually not to become a detective to find your problems in the code, how to make your login meaningful, then how to set up your CI CD pipelines. I prepared some beautiful examples. And then how to secure your Docker images. After that, I will show some links then you can follow later on. So let's start with exceptions. We don't really want to show to our customers 500 error because it's like you can see this error and then you know that something is wrong on the server side. But as you're a customer, you cannot follow up on that. You can contact the administrator of the website. Usually there is an email to contact. And then what to do with that? We don't really want to share this information with the customer. We want the customer to see some proper exception handling. If something went wrong, then they would know what to do, how to follow up for that. We are usually trying to handle exceptions in the manner that we are either silencing them or maybe just using the try accept block to catch the exception and then printing something on our side, on the server side to see. So what's the proper way of handling exceptions? We can try to catch exceptions and the basic exceptions. Catching exceptions by official definition of exceptions in the official documentation is not a really good idea because it's too broad and catching the basic exception is even dangerous. So it's highly not recommended to catch any basic exceptions. So guess what will be printed here? If you are trying to get some input from the terminal, you run this code and then you're trying to catch exception and the basic exception. Then you are interrupting your script and then what would be printed next? You will get the basic exception printed on the screen that keyboard interrupt happened. Why is it happening? Let's take a look into hierarchy of the exceptions. It's way longer. I just cropped the first top section. So basically basic exception has more than exception. That's why it's not recommended to handle, to try to catch basic exception because you might catch more than just exception which you're trying to catch. You will get also keyboard interrupt, system exit and generator exit. So in this case we interrupted our script and then we got keyboard interrupt. In this case if you will silent the exception then what will happen next? Highly not recommended to use basic exceptions. So how to actually handle exceptions? Let's try to take a look at this example. We're trying to catch exception of the exception class and then we will print some exception on the screen and then we need to change message of the exception to have something different like my custom message. And then we will see on the screen that you're handling the above exception and other exception occurred. That's not the ideal way because you are trying to handle exception and then you failed while handling the exception. That could happen also if you're trying to do some actions which might throw the exception on the handling but this is super high not recommended. So what can we do if we really need to change the exception message? We can use from. At the last line you can see that we are trying to rise the exception with the custom message so it's going to be a different exception but we are rising it from exception which we already got. So at the screen we will see the above exception was the direct cause of the following exception and then you can see the correct trace back, you will see the previous exception and the current exception and then you will see your awesome printed message. The best way to handle exceptions is to be more specific on the exceptions. For example you can create your custom exceptions with the custom message and then you can rise your custom exceptions whenever it's needed. You can try to catch your custom exceptions or specific exceptions to be more specific not to use a broad version of just exception handling of exception and then you can still print them on the screen. Here at Europe Python in Basel I saw a talk from Mario Cajero, exceptional exceptions, it was really, really good. There are more tips to learn and you can see the video from PyCon US in here, highly recommended to watch it and to go through all of the tips. The next step is logging, how to make your logging meaningful. Let's take a look. What are the logs? In the 12 factor app which was super popular a few years ago, everyone was talking about 12 factor app. Nowadays I heard some talks about that, some people mentioned that but not that much anymore. But actually they have really good content and feel free to go to the 12 factor app and check the logs section. So they're saying that we have to treat logs as events, streams and logs provide visibility into the behavior of a rainy application. So let's take a look at the basics of logs. What should log include? I did some research and then I found out that the main login attributes are when, where, what, who and the outcome itself. So when would be the just a timestamp when the log entry happened, then where did it happen, maybe passed to the file, then what actually happened if it was exception or maybe just information, then who did this action. If there is a user then maybe some kind of ID information but no customer information at all. I make sure that you're following all the GDPR principles and you're not logging any customer information. And then also important to have an outcome, the message itself. So how can we improve our logs? Usually we are having this type of logs. We are using the standard logger and then we are putting everything into a one log message. So I added here some important information about the conference name, the talk name and also some random key ID. After printing this, we will see something like this. So then we have a log level, we have where this log message happened. We can also add the timestamp super easily. And then we have the entire message. This is not super useful if we're trying to find out what actually happened on our system. So how can we improve that? How can we make the log message more parsable for DevOps team if we have a DevOps team or we are our own DevOpses and we're trying to build some boards on the top of the log messages? In this case, we cannot really check by the conference name because for example, if we separate by commas the log message, then we will still get some random stuff. For example, in the talk name, we have comma inside of the talk name. If we try to separate by some other ID or maybe we will have ID friendly, then we need to make sure that everyone uses the same structure of the logs, the same quality of the logs, which is quite hard to establish. So what actually I tried in my teams, we used the struct log for that. There are also great talks about the struct log and there are lots of examples in official documentation. I will show you some examples which I use myself. So super easy, you just get the logger instance and then you try to use the same, we are trying to use the same information from the previous log. So we will have also key ID, conference name and talk name. But the structure in here is a key value pair. What will we get actually? We will have the log message and then keywords. We can easily parse by the name of the keyword and then we can get the value to build the dashboards on that, which will show us way more than just going through millions of logs from just our servers. So yes, definitely recommended to use the struct log. Let's take a look at the features of it. For example, here we can bind some important keys to every log entry. In this case, for example, I have the entire big file with all the logs, log entries, like info, exception, whatever we need to log. And then I need to have conference name, talk name and key ID in every log entry. Obviously, I would not write, like just copy paste in the entire line to every log message. It's possible to use just bind and then in every log message, we will have the same keywords already there. So we just don't need to specify them over and over again through the all log messages. Then we will get the same exception handling and then we will see our beautiful log message. For the development, we can have this highlight on. We can use colorama for that. And for servers, we just don't need that. Also we can use JSON format for logs. Then we will see event as a message of the log. Then we will see the Euro Python custom log level. I will show that in a minute. And then the name of the logger and the time step and many more. So let me show you some code. I hope you can see my screen with the pie charm. This is a basic idea. Then let's take a look at the processors. So what do we have in here? We have some password in the log message, which we wanted to print, but actually we don't want to have any customer information in here. Because of that, we can use a super custom processor. We can write the processor ourselves and then after that we will have the password censored. We can also use tracing from the struct log. We can, for example, pass the trace ID from one service to the other one and then just add it to the log message, which would show us how this actually error transferred from one message to the other one. And also many more features. You can add so many processors and features into this one. But we are running out of time. So I want to show you a bit more of the front features. I wrote everything in the blog post. I will share links later on so you can follow all the ideas later on. And this is a really good talk about the struct log with more examples. Effective CI CD. I prepared also some examples for you and I will share them after this talk. Continuous integration and why do we need it. It will provide you the test coverage, reliability, fault isolation, transparency, code quality, faster development and code review improvements. You can automate everything. There are different examples of how to integrate your checks into the CI and I will show you some super tiny example with the GitHub actions, which is super easy, simple to set up. You just go to actions and then you click on the specific CI, which is already proposed and then you can edit it easily. I have chosen the Python application setup. Here's the link to the test setup. You can check it out afterwards and I will show you how it looks at a moment. So basically we have GitHub workflows and then we can have our beautiful workflow. I added everything in here. The only difference between those different entries is this continue on error. If you want to make sure that developer cannot really merge the code before the CI is passing, you have to delete this line, continue on error. But if you don't really care, you just want to see how it looks on the CI, then you can just continue on error and run all of the checks before they're fixed. But then we cannot really make sure that CI is actually passing. So in here we have the CI part and then here are all the jobs. This one is the only one which is mandatory for my code and all the other ones are not really passing because I didn't really care to fix, just to show you the difference. Let's check the py lint. Py lint is not passing because there is something which is missing in here. Also I sort is not passing, but actually my code was already merged because I didn't make that mandatory. So I want to make sure that those steps which are important for you to check are mandatory. And there is also really nice tool which is called documentation coverage tool interrogate. You can check that a bit later. I don't have any documentation right now. So it's obviously failing because actual result is zero. You can use it like this interrogate and then just specify what you want to check if there's enough documentation. Then the Docker file. Just a couple of words about the Docker file, how to secure your Docker file and Docker images. I also prepared a lot of examples which you can check later on after the talk. So the basic advice is not to use the root user. Then use trusted well-known images. Check on the Docker hub if the image has enough downloads and it's an official source. Then use copy instead of add. Although they have super similar functionality, but then add can have extra functionality which you will not expect. So make sure that you're using copy. And then try to link your Docker file. You can use that on the CI. It's super easy and simple. I use one tool which is called haddolint. It's a Docker file linter. It will link your code super easily. It will tell you what's wrong in the code. This is just an example for them. It's super easy to set up on the CI. So in this Docker file, I made a mistake. I did the pip install and then just a package name. I didn't pin the dependencies. And because of that, my linter advised me to change that. Whenever I changed, then it was passing. Also this linter will let you know whenever you're using add instead of copy. So you don't need to worry about that as well. And the last but not least, you can also check vulnerabilities in your code. I didn't try it myself. But I will definitely try. And I saw that there is already interesting tool which is sort of for free. And I'm going to give it a try. And a bit more hints how to use not the root user in the Docker files. You need to create a user. You need to create a group. And why do we actually do that all? Because we would like to follow the principle of the least privilege here. And that means that we should give access only to resources to perform their required function. So if they need to perform just a function with no root user, obviously, we will not give them root access. And I have some recommendation of the books for the further reading. And also you can find all the links here in my personal blog. So thank you for listening. And I believe we don't have so much time for questions. No, actually we are very good on time at the moment. But I don't see many questions at the moment because in the chat, nobody has asked any. I think you just gave them an overview so that they have the time to think about this. I really like the Docker tips that you gave. And there was also another Docker talk yesterday. So if somebody is into Docker, then they should look at the other talks as well. So it was a really good talk. You highly recommend that you check it out. Well, yes. So the only thing that's left to me is to give you a round of applause.
 People from Germany might know you from the that you're the organizer of the Pi Berlin  meetups and that you've been around PyConDE as well a lot.  And of course we've seen you at other Europe items as well.  Today you want to talk about how to make your code production ready.  Yes, exactly.  It's a super interesting topic because when you do stuff in production like now things  can break all the time.  So how to make sure that that works is interesting for us.  Are you ready to do your screen share?  Yes.  Then I hope to have a nice talk.  So actually I'm going to talk about production ready code and whether our code is ready to  be deployed somewhere or not.  A few words about myself.  Thank you for a nice introduction.  I'm basically connected from Berlin and I'm a Pi Berlin organizer since already a year  and a half.  I have 10 years in software development and seven years in Python.  Actually there is a very interesting story which I will quickly share before we start.  I'm here in Python because of the community most of the time on the conferences and I'm  super happy that once our customer, I was a former C++ developer and once customers  said that now you are a Python developer, you have to learn Python.  I didn't understand how great was it because back then I was in Ukraine and there was not  much of a community thing in Python at all.  There was C++, Java and different meetups but not Python at all.  And then when I moved to Berlin, I realized that there is so much to learn and to share  with the community.  Everyone is so supportive.  So I'm super happy that I'm here right now and thank you for all the support.  I know it's remote.  I don't see anyone here right now but I can feel the support.  Thank you so much.  You're all invited to the next Pi Berlin meetup and if you're willing to speak for us, just  feel free to drop by just a message or maybe just join the Pi Berlin channel.  So what exactly does production mean to you?  What do you mean by production ready code?  What's the difference of production ready and non-production ready code?  Actually, I read the book, release it.  It's great and there is a favorite quote of this book is, you work so hard on your project.  It looks like all the features are actually complete and most even have tests.  You can breathe a sigh of relief, you're done or are you?  There is no checklist what to go through on your code if it's ready for production or  not.  Can you deploy already to your customers?  So I prepared some checklists for you.  And before starting that, I would like to emphasize that the only difference between production  and not production code is that there is a customer behind the computer or behind the  device who is trying to run your code.  The difference is that we are developers.  We know how our code is supposed to work.  We know where to click.  We know what to do on the website or the application or whatever we are building.  But the customer doesn't know.  The only difference that code could break and it will break, we have to make sure that  we know how to investigate, how to find the problem and fix it, how to provide a solution  to our customers.  And here are the checkpoints what to check before going to production.  So first of all, I would talk about exception handling.  There was also a very good talk yesterday about exception handling and I will also share  some tips about that.  And then how actually not to become a detective to find your problems in the code, how to  make your login meaningful, then how to set up your CI CD pipelines.  I prepared some beautiful examples.  And then how to secure your Docker images.  After that, I will show some links then you can follow later on.  So let's start with exceptions.  We don't really want to show to our customers 500 error because it's like you can see this  error and then you know that something is wrong on the server side.  But as you're a customer, you cannot follow up on that.  You can contact the administrator of the website.  Usually there is an email to contact.  And then what to do with that?  We don't really want to share this information with the customer.  We want the customer to see some proper exception handling.  If something went wrong, then they would know what to do, how to follow up for that.  We are usually trying to handle exceptions in the manner that we are either silencing  them or maybe just using the try accept block to catch the exception and then printing something  on our side, on the server side to see.  So what's the proper way of handling exceptions?  We can try to catch exceptions and the basic exceptions.  Catching exceptions by official definition of exceptions in the official documentation  is not a really good idea because it's too broad and catching the basic exception is  even dangerous.  So it's highly not recommended to catch any basic exceptions.  So guess what will be printed here?  If you are trying to get some input from the terminal, you run this code and then you're  trying to catch exception and the basic exception.  Then you are interrupting your script and then what would be printed next?  You will get the basic exception printed on the screen that keyboard interrupt happened.  Why is it happening?  Let's take a look into hierarchy of the exceptions.  It's way longer.  I just cropped the first top section.  So basically basic exception has more than exception.  That's why it's not recommended to handle, to try to catch basic exception because you  might catch more than just exception which you're trying to catch.  You will get also keyboard interrupt, system exit and generator exit.  So in this case we interrupted our script and then we got keyboard interrupt.  In this case if you will silent the exception then what will happen next?  Highly not recommended to use basic exceptions.  So how to actually handle exceptions?  Let's try to take a look at this example.  We're trying to catch exception of the exception class and then we will print some exception  on the screen and then we need to change message of the exception to have something different  like my custom message.  And then we will see on the screen that you're handling the above exception and other exception  occurred.  That's not the ideal way because you are trying to handle exception and then you failed while  handling the exception.  That could happen also if you're trying to do some actions which might throw the exception  on the handling but this is super high not recommended.  So what can we do if we really need to change the exception message?  We can use from.  At the last line you can see that we are trying to rise the exception with the custom message  so it's going to be a different exception but we are rising it from exception which we  already got.  So at the screen we will see the above exception was the direct cause of the following exception  and then you can see the correct trace back, you will see the previous exception and the  current exception and then you will see your awesome printed message.  The best way to handle exceptions is to be more specific on the exceptions.  For example you can create your custom exceptions with the custom message and then you can rise  your custom exceptions whenever it's needed.  You can try to catch your custom exceptions or specific exceptions to be more specific  not to use a broad version of just exception handling of exception and then you can still  print them on the screen.  Here at Europe Python in Basel I saw a talk from Mario Cajero, exceptional exceptions,  it was really, really good.  There are more tips to learn and you can see the video from PyCon US in here, highly recommended  to watch it and to go through all of the tips.  The next step is logging, how to make your logging meaningful.  Let's take a look.  What are the logs?  In the 12 factor app which was super popular a few years ago, everyone was talking about  12 factor app.  Nowadays I heard some talks about that, some people mentioned that but not that much anymore.  But actually they have really good content and feel free to go to the 12 factor app and  check the logs section.  So they're saying that we have to treat logs as events, streams and logs provide visibility  into the behavior of a rainy application.  So let's take a look at the basics of logs.  What should log include?  I did some research and then I found out that the main login attributes are when, where,  what, who and the outcome itself.  So when would be the just a timestamp when the log entry happened, then where did it  happen, maybe passed to the file, then what actually happened if it was exception or maybe  just information, then who did this action.  If there is a user then maybe some kind of ID information but no customer information  at all.  I make sure that you're following all the GDPR principles and you're not logging any  customer information.  And then also important to have an outcome, the message itself.  So how can we improve our logs?  Usually we are having this type of logs.  We are using the standard logger and then we are putting everything into a one log message.  So I added here some important information about the conference name, the talk name and  also some random key ID.  After printing this, we will see something like this.  So then we have a log level, we have where this log message happened.  We can also add the timestamp super easily.  And then we have the entire message.  This is not super useful if we're trying to find out what actually happened on our system.  So how can we improve that?  How can we make the log message more parsable for DevOps team if we have a DevOps team or  we are our own DevOpses and we're trying to build some boards on the top of the log messages?  In this case, we cannot really check by the conference name because for example, if we  separate by commas the log message, then we will still get some random stuff.  For example, in the talk name, we have comma inside of the talk name.  If we try to separate by some other ID or maybe we will have ID friendly, then we need  to make sure that everyone uses the same structure of the logs, the same quality of the logs,  which is quite hard to establish.  So what actually I tried in my teams, we used the struct log for that.  There are also great talks about the struct log and there are lots of examples in official  documentation.  I will show you some examples which I use myself.  So super easy, you just get the logger instance and then you try to use the same, we are trying  to use the same information from the previous log.  So we will have also key ID, conference name and talk name.  But the structure in here is a key value pair.  What will we get actually?  We will have the log message and then keywords.  We can easily parse by the name of the keyword and then we can get the value to build the  dashboards on that, which will show us way more than just going through millions of logs  from just our servers.  So yes, definitely recommended to use the struct log.  Let's take a look at the features of it.  For example, here we can bind some important keys to every log entry.  In this case, for example, I have the entire big file with all the logs, log entries, like  info, exception, whatever we need to log.  And then I need to have conference name, talk name and key ID in every log entry.  Obviously, I would not write, like just copy paste in the entire line to every log message.  It's possible to use just bind and then in every log message, we will have the same keywords  already there.  So we just don't need to specify them over and over again through the all log messages.  Then we will get the same exception handling and then we will see our beautiful log message.  For the development, we can have this highlight on.  We can use colorama for that.  And for servers, we just don't need that.  Also we can use JSON format for logs.  Then we will see event as a message of the log.  Then we will see the Euro Python custom log level.  I will show that in a minute.  And then the name of the logger and the time step and many more.  So let me show you some code.  I hope you can see my screen with the pie charm.  This is a basic idea.  Then let's take a look at the processors.  So what do we have in here?  We have some password in the log message, which we wanted to print, but actually we  don't want to have any customer information in here.  Because of that, we can use a super custom processor.  We can write the processor ourselves and then after that we will have the password censored.  We can also use tracing from the struct log.  We can, for example, pass the trace ID from one service to the other one and then just  add it to the log message, which would show us how this actually error transferred from  one message to the other one.  And also many more features.  You can add so many processors and features into this one.  But we are running out of time.  So I want to show you a bit more of the front features.  I wrote everything in the blog post.  I will share links later on so you can follow all the ideas later on.  And this is a really good talk about the struct log with more examples.  Effective CI CD.  I prepared also some examples for you and I will share them after this talk.  Continuous integration and why do we need it.  It will provide you the test coverage, reliability, fault isolation, transparency, code quality,  faster development and code review improvements.  You can automate everything.  There are different examples of how to integrate your checks into the CI and I will show you  some super tiny example with the GitHub actions, which is super easy, simple to set up.  You just go to actions and then you click on the specific CI, which is already proposed  and then you can edit it easily.  I have chosen the Python application setup.  Here's the link to the test setup.  You can check it out afterwards and I will show you how it looks at a moment.  So basically we have GitHub workflows and then we can have our beautiful workflow.  I added everything in here.  The only difference between those different entries is this continue on error.  If you want to make sure that developer cannot really merge the code before the CI is passing,  you have to delete this line, continue on error.  But if you don't really care, you just want to see how it looks on the CI, then you can  just continue on error and run all of the checks before they're fixed.  But then we cannot really make sure that CI is actually passing.  So in here we have the CI part and then here are all the jobs.  This one is the only one which is mandatory for my code and all the other ones are not  really passing because I didn't really care to fix, just to show you the difference.  Let's check the py lint.  Py lint is not passing because there is something which is missing in here.  Also I sort is not passing, but actually my code was already merged because I didn't make  that mandatory.  So I want to make sure that those steps which are important for you to check are mandatory.  And there is also really nice tool which is called documentation coverage tool interrogate.  You can check that a bit later.  I don't have any documentation right now.  So it's obviously failing because actual result is zero.  You can use it like this interrogate and then just specify what you want to check if there's  enough documentation.  Then the Docker file.  Just a couple of words about the Docker file, how to secure your Docker file and Docker images.  I also prepared a lot of examples which you can check later on after the talk.  So the basic advice is not to use the root user.  Then use trusted well-known images.  Check on the Docker hub if the image has enough downloads and it's an official source.  Then use copy instead of add.  Although they have super similar functionality, but then add can have extra functionality  which you will not expect.  So make sure that you're using copy.  And then try to link your Docker file.  You can use that on the CI.  It's super easy and simple.  I use one tool which is called haddolint.  It's a Docker file linter.  It will link your code super easily.  It will tell you what's wrong in the code.  This is just an example for them.  It's super easy to set up on the CI.  So in this Docker file, I made a mistake.  I did the pip install and then just a package name.  I didn't pin the dependencies.  And because of that, my linter advised me to change that.  Whenever I changed, then it was passing.  Also this linter will let you know whenever you're using add instead of copy.  So you don't need to worry about that as well.  And the last but not least, you can also check vulnerabilities in your code.  I didn't try it myself.  But I will definitely try.  And I saw that there is already interesting tool which is sort of for free.  And I'm going to give it a try.  And a bit more hints how to use not the root user in the Docker files.  You need to create a user.  You need to create a group.  And why do we actually do that all?  Because we would like to follow the principle of the least privilege here.  And that means that we should give access only to resources to perform  their required function.  So if they need to perform just a function with no root user, obviously,  we will not give them root access.  And I have some recommendation of the books for the further reading.  And also you can find all the links here in my personal blog.  So thank you for listening.  And I believe we don't have so much time for questions.  No, actually we are very good on time at the moment.  But I don't see many questions at the moment because in the chat,  nobody has asked any.  I think you just gave them an overview so that they have the time to think about this.  I really like the Docker tips that you gave.  And there was also another Docker talk yesterday.  So if somebody is into Docker, then they should look at the other talks as well.  So it was a really good talk.  You highly recommend that you check it out.  Well, yes.  So the only thing that's left to me is to give you a round of applause.
Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_0_71.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_72_155.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_156_1043.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_1044_9407.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_9408_9623.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_9624_23195.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_23196_23255.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_23256_25475.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_25476_25571.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_25572_25739.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_25740_27083.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_27084_27131.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_27132_28319.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_28320_28763.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_28764_29135.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_29136_29903.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_29904_30059.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_30060_31355.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_31356_31403.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_31404_31499.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_31500_31751.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_31752_32507.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_32508_33023.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_33024_33923.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_33924_33995.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_33996_34055.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_34056_34475.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_34476_34691.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_34692_35399.png Anastasiia_Tymoshchuk_-_Can_we_deploy_yet__Fgyk8P0IJsU_35400_35489.png