Best practices for production-ready Docker packaging
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Subtitle |
| |
Title of Series | ||
Number of Parts | 130 | |
Author | ||
License | 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 | |
Identifiers | 10.5446/49984 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
QuicksortProduct (business)Shared memorySoftware developerTouchscreenProcess (computing)Meeting/Interview
00:54
Computer networkProcess (computing)Link (knot theory)MereologyInformation securityComputer-generated imageryServer (computing)RootInstallation artPhysical systemQuicksortMaxima and minimaChecklistInformation securityRootPhysical systemCodeSoftware bugLine (geometry)Process (computing)Cartesian coordinate systemMereologyBitElectronic mailing listComputer fileIdeal (ethics)Electronic program guideCASE <Informatik>Term (mathematics)Default (computer science)Multiplication signPoint (geometry)Slide ruleLink (knot theory)Goodness of fitOrder (biology)InternetworkingScripting languageRight angleServer (computing)Boolean algebraVirtual machineNeuroinformatikWave packetResultantMobile appWeightAnalytic continuationCycle (graph theory)WebsiteLattice (order)Complex (psychology)Software developerProduct (business)Directory serviceInformation privacyCovering spaceOperating systemSocial classComputer animation
09:50
Computer-generated imageryBuildingDisintegrationPhysical systemDisk read-and-write headError messageCrash (computing)CodePoint (geometry)Integrated development environmentProcess (computing)MaizeBytecodeSource codeCompilerBuildingComputer programmingRevision controlSoftware developerBranch (computer science)Crash (computing)Projective planeCore dumpDifferent (Kate Ryan album)LoginExtension (kinesiology)DatabaseProcess (computing)Error messageMultiplication signSoftware bugSoftware testingCodeOperator (mathematics)QuicksortPhysical systemINTEGRALInterpreter (computing)Division (mathematics)Line (geometry)Functional (mathematics)Cartesian coordinate systemBytecodeComputer fileScripting languageStability theoryMereologyoutputTraffic reportingException handlingIntegrated development environmentSource codeInformationFigurate numberModule (mathematics)Sampling (statistics)Slide ruleProduct (business)Windows RegistryData storage deviceServer (computing)File systemRun-time systemVirtual machinePoint (geometry)MiniDiscRandomizationRow (database)NeuroinformatikBuffer overflowStapeldateiDirection (geometry)Ocean currentPolarization (waves)Local ringMetropolitan area networkParsingGastropod shellLevel (video gaming)Computer animation
18:46
BytecodeSource codeComputer-generated imageryCompilerMaizeProcess (computing)Information securityState transition systemRevision controlPhysical systemBuildingFocus (optics)Installation artCache (computing)Default (computer science)SpacetimeLink (knot theory)Slide ruleEmailTwitterWebsiteMiniDiscUtility softwareRevision controlDefault (computer science)Point (geometry)Multiplication signCodeMathematical optimizationStability theorySoftware testingDistribution (mathematics)Programmer (hardware)Alpha (investment)Information securityProcess (computing)Library (computing)Cartesian coordinate systemFrequencySpacetimeSoftware bugLattice (order)Physical systemTerm (mathematics)Integrated development environmentTwitterQuicksortEmailWebsiteSlide ruleLink (knot theory)Different (Kate Ryan album)Electronic program guideFerry CorstenOrder (biology)Computer configurationPhysicalismLatent heatCompilerMereologyModule (mathematics)Line (geometry)Computer fileSound effectGoodness of fitNeuroinformatikInstallation artBinary codeSoftware maintenanceWindow2 (number)ChainBand matrixFile systemRun time (program lifecycle phase)Message passingHash functionMacro (computer science)BuildingMathematicsCache (computing)Computer animation
27:42
Revision controlComputer fileMathematicsApproximationInstallation artPhysical systemCodeLine (geometry)Multiplication signInteractive televisionParameter (computer programming)DampingSource codeDomain nameCache (computing)WordMeeting/Interview
30:06
BytecodeSource codeComputer-generated imageryCompilerCellular automatonMaizeInstallation artInformation securityOperator (mathematics)InjektivitätComputer fileCodeMultiplication signPort scannerSoftware testingGoodness of fitPhysical systemDefault (computer science)AreaWebsiteSource codeBytecodeFile systemComputer animation
31:38
VirtualizationRoundness (object)Video gamePoint (geometry)BlogMeeting/Interview
Transcript: English(auto-generated)
00:07
Hello Itamar. Itamar Turner-Trowick is a trainer and software developer and writes about Python at pythonspeed.com. And you've been doing Python for over 20 years, is that true?
00:24
Yeah, since 1999. That's amazing. And you still enjoy it a lot. I do, yes. Today you plan to show us something about Docker. Yeah, it's sort of the process that I use for Docker packaging
00:43
to make it ready for production because there's a lot of details. It's complicated enough, you need a whole process for it. Okay, let's see if your screen share works and then I'd say we should start the talk. So yeah, today I'll be talking about production-ready Docker packaging for Python. And again, my name is Itamar.
01:02
And the first thing you need to understand is that Docker packaging is really complicated. There's a lot of details you need to get right. And the reason is that it builds on basically 50 years of technology from Unix in the 70s to Docker and modern Python packaging in the 2010s.
01:23
I don't really want to talk about the 2020s. And so each of these technologies which accumulated over the years has its own assumptions about how things work, its own design mistakes, like however useful these technologies are, they're not perfect. They all have certain defaults which may or may not be correct
01:43
in the case of running within Docker. And so the accumulation of all these technologies sort of intersects within the Docker packaging for your Python application. And so you just end up having to get a lot of details right in order to make something that's truly production-ready.
02:01
And so this is not simple. And so the result is that there's basically, I cannot cover all this material in one half-hour talk. We only have 30 minutes and that includes questions. I have my own personal list of Docker packaging best practices
02:20
has at least 60 items on it and it keeps growing. When I teach this as a training class, these days I would do it like a day and a half because just there's so much material and even then I can't quite cover all the details, but can do it in some depth. And so within a talk, we can't actually learn all the best practices.
02:42
But what we can do is learn, go over a process for how you do this packaging. And the reason you need a process is in part just because of this complexity, like there's a lot of details there's a lot of things that are easy to miss. It's easy to get sidetracked by certain aspects of the problem like, oh, my image is huge
03:01
and then forget about other aspects like security. But also because Docker packaging is probably a thing you're going to be doing on your job. And if you're working a job, there's usually lots of other things that you need to be doing. There might be some critical bug that interrupts you, you have to go to a meeting. And so this isn't the sort of thing
03:22
where you spend half an hour, finish it and it's done and it's perfect. You're going to have to put a little bit more time into it. And so what you need is a process, a process that will help you do iterative development so that you can stop at any point and come back later.
03:41
That helps you focus on doing the important parts first and reminds you what the important parts are. And that builds, so each step builds on the previous one so that you're sort of have this cycle of continuous improvement. So I'm going to go through this process and steps in the process. And for each step, I'm going to give a example of one of the best practices and list a few more
04:02
because I don't have the time to actually go through all of them. What I'm going to do is at the end of the talk will be a link to the free guide I have on my website. It's at least 30 articles and it covers a lot of these best practices in far more detail. And so you don't have to try to remember all this.
04:22
There will be a link to the slides and a link to a much more detailed guide for those best practices that I don't cover, at least most of them. So here's an overview of the process. And this is what's going to structure the rest of the talk.
04:41
We start out with getting something working and then move on to security. And eventually, the last thing you do is you optimize your image so you can build faster and make it smaller. And the idea here is you want to start with the most important parts. Like security is fairly critical in most applications.
05:02
You probably want to do it first. Having a small image is a thing you want to do or probably like not immediately. It's lower than every list. This is sort of a generic list. And in your particular application, in your situation, the order might be different. So this is a starting point. Maybe that reproducible builds, for example,
05:21
are really critical for what you're doing. And so you might do that first. And this is also a process that sort of guides you the first two or three times that you're doing that. And eventually, you'll be doing a lot of these best practices automatically. And so you won't need to think so much
05:40
in terms of this exact order. You might just automatically do a whole bunch of step four right from the beginning. But even then, it's useful to have sort of a checklist of here's all the things I have to do because there are so many details to get right. So the first step in packaging your Docker image
06:00
is just getting something working. It doesn't matter how good your packaging is, how secure and efficient and small and correct, it doesn't actually run your application. It doesn't run your server. This is sort of the bare minimum to have said you've succeeded at doing something useful. And so the first step is just get your application working even if it's done in a sort of not an ideal way
06:23
because it's just your starting point. So in this example Dockerfile, I'm using the Python 3.8 slimbuster Docker image as a base image, copying in all the files in the current directory, running pip install to install the code, and then I use the entry point to say,
06:40
when you run this image, run this script to start up the container. So it's not a... As you'll see, even from the examples, this has a bunch of flaws, but it's a starting point. You have to start somewhere. The next step is security.
07:02
Before you can feel comfortable deploying something publicly where anyone on the internet can access it, you probably should be making sure that that application is as secure as you can make it. Otherwise, you basically always have this worry that someone will break into it
07:21
and get access to your private data, modify your website, take it down. And so since security is sort of a minimal prerequisite for running anything and deploying anything, it's probably a good first step in terms of what best practices you should implement.
07:40
So again, I can't cover all the security best practices, but for each of these steps, I'm going to give an example. And for security, one best practice is don't run as root. Containers are a way to isolate processes from each other and from the host operating system,
08:00
but they're only isolated in a limited way. A virtual machine is much more isolated. And so when you run a Docker image and create a container by default, most Docker images will run as root. So if you run nginx, it runs as root, the official Python image runs as root,
08:21
and the Ubuntu image runs as root. And the problem with running as root is that it gives rather more access than one would like to various capabilities of the operating system. So if someone manages to take over your process remotely, if your process is running as root, even in a container,
08:42
it is much easier for an attacker to escape the container and escalate their access and take over your whole computer. And so good security best practices don't run as root. And so in this example, I've updated the Docker file so that after choosing the base image, we run a command that creates a new user called appuser.
09:02
And then we use a Dockerfile user command to say all later commands should run as this new user. So for example, when you copy in files, they'll now be owned by this new user. When you run pip install, it'll run as that user. When you start up the container, it will run the container as this non-root user.
09:23
And so with two, three extra lines of code in your Docker file, you'll now have a much more secure Docker image. Again, there's plenty of other security best practices. I won't go into them, but the guy I downloaded at the end has more details about many of them.
09:43
So now that you have a working image that is hopefully somewhat secure, you might just start thinking about automation. Up to this point, you have this Docker file. You build it manually. You could deploy it manually if you want. But over time, you don't want to have to rebuild
10:02
your application every time someone merges a pull request. You might have other team members who are using this code base, and they want things built automatically. They don't care about the details. And so a good next step is to automate the build so that your builder CI system automatically builds Docker images and pushes them to the image registry,
10:22
the server that stores your Docker images. So here's a sample batch script that does that for you. Do set minus EUO pipe fill, which is a line you should have at every batch script just so it stops running when there's errors. We run our tests, do Docker build to build the image,
10:41
and then do Docker push to push your image. So if you put this in your builder CI system, every time you trigger a build, it'll also build and push your Docker image. And so once you start doing this, you have to start thinking about the way that you structure development
11:02
and the way your development process integrates with your build system. So for example, a common development process is to have feature branches. So if you have an issue 123, then the developer working on that issue will create a branch 123 and do the work in that, add the feature,
11:24
fix the bug in that branch, and then do a pull request. And then your build system will automatically build and run the tests and do a build from that pull request. And you might want to build a Docker image for every pull request
11:40
because you might want to test that Docker image manually, automatically, maybe have an integration test. And if you use the scripts that I showed in the last slide, which is fairly simplistic, what's going to happen is you have this pull request with a branch, and then you're going to build that image and then you're going to push it. And it's going to overwrite your stable release Docker image
12:03
because you're always giving the images you push the same name. Like you're always pushing to your image latest, which means random pull requests are going to overwrite your official release Docker image. So what you want to do is you want to make sure that Docker images built by pull requests don't stomp on your release Docker image.
12:21
And one easy way to do this is to name your images based on your git branch. So in this variant on the build script, I'm getting the current git branch using the git rev parse command. Just for the record, I don't remember the git commands ever. I always use stack overflow or look at my notes
12:43
because they're impossible to remember. So you get the git branch, and then you say, I'm going to name this man such that I'm going to name my new image such that the part after the colon, the tag, is the same as the git branch. And so if you have branch 123 more cowbell,
13:00
now this image will be your image colon 123 more cowbell, and it won't overwrite your production image, your stable image. Again, plenty of other best practices that you can do in your CI system, which we'll go into today. So at this point, you have an image that runs your application.
13:21
It is hopefully secure, and it has automated builds. And so now that you have automated builds, you're starting to accumulate multiple images. You have your image you built last week, your image you built today, the image you built at Polar Quest, the image your teammate created. You're running it in production maybe. People are running different versions of it.
13:42
And so now you're more likely to see errors. You're more likely to have to try to debug errors. So a good next step at this point is to work on making your Docker image easier to identify and also easier to debug. And so here's an example best practice
14:00
for making your Docker image, or even your Python code in general, while you build one. So if you have a bug in your Python code, something went wrong, bad input, unexpected issue somewhere, you'll get an exception thrown. If it's something that can't get handled, then what will usually happen is it'll get converted to a traceback. The traceback will be stored in the logs.
14:22
And then if your server crashes, you can go look in the logs. If it doesn't crash and you have a bug report, you can go look in the logs. You look in the logs and you say, oh, it was this function calling this function, this line of code through a zero division error. That gives you a really good starting point for figuring out what went wrong
14:40
because you know where in your code this error originated from. If you have a bug in C code, that's not what's going to happen. If you have a bug in C code, your program is going to crash silently. And the Python interpreter is written in C and chances are many of the third-party extensions you're installing are also using C code,
15:01
whether it's a database adapter or map file that was Empire. Most projects will end up using some C code. And so if you have a crash in your C code, you might get a core dump, but the file system for your Docker container is ephemeral and typically will just get thrown away once your process crashes.
15:20
So your process crashes, core dump may not, and then the file system disappears and then now you have nothing. You have no logs, no core dump. All you know is that your program crashed. And so it's extremely difficult to debug code in this situation. Luckily, Python had a solution for this. There's a module called fault handler. And what it does is it adds some hooks
15:41
so that if your program crashes in C code, it will do a best effort to print a trace back of the Python code where you crashed. And that means as crashes in C code give you the same information that crashes in Python code to a nice Python trace back, which allows you to sort of figure out where in your code the bug came from.
16:02
You can say, oh, this came out of the database adapter. There's a bug in the database adapter instead of just having no idea where the problem came from. And the easiest way to use fault handler is to set an environment handler, an environment variable called Python fault handler. So you set it to one. You can do this in your shell
16:21
for running code locally. You can, in your Dockerfile, you can use the env command. So you do env python fault handler equals one. It's one extra line in your Dockerfile. And from now on, anytime you have a crash in your C code, you'll have a much easier time debugging it. Again, there are other best practices you can use to make your image
16:41
easier to identify and easier to debug. So now onto step five. So you have a working container. It runs your application. It's secure. It gets built automatically. You made it easier to identify and debug. And so the next step is to say, well, how can we make it run better,
17:02
run faster, be less likely to have issues in the first place? And so that means things like making it start up faster, which can, in certain applications, make a big difference, shut down faster, which can, if you're deploying new versions of your code,
17:21
you might fast shut down, makes it easier to deploy a bug fix, allow your runtime environment to detect if your process is frozen, and that sort of thing. And so one example of a best practice for operational correctness is compiling your bytecode. So when your Python interpreter
17:41
runs your source code, it doesn't actually run the Python source code, the text that you wrote in that PY file. What actually happens is it parses the source code and then creates bytecode, which is what the interpreter in the CPython virtual machine runs, interpreter virtual machine. And so it takes that bytecode
18:01
and writes it to a .pyc file and stores it on disk. The next time you run your Python application, instead of having to parse the source code and convert it to PYC, it can load the PYC directly. That can speed up your startup. So if your Docker image doesn't have PYCs for all of your source code, that will mean slower startup
18:20
because every time you start the container, it's going to have to parse the source code. And when you're running on your local computer, this is not really a thing you think about because your file system is persistent. So you run a program once, it creates the PYCs, you run the program a second time, and the PYCs are there and they can get used and your startup is faster.
18:41
When you're running in a Docker image, every time you're starting a container from a Docker image, every time you start a new container, it starts from a pristine copy of whatever was in the Docker image file system. And so every time you start a new container, if there's no .pycs, it'll create them. And then when the process exits
19:02
and the container exits, that file system will get thrown away. And the next time you start the container, it'll again start with the .pycs. And so if you're packaging something for Docker, you may have to explicitly create those .pyc files and have faster startup. There's a couple of example lines here you can add to your Docker file.
19:20
One of them compiles the code that you've installed, and typically pip does this, so you may not have to do this most of the time. If you just copy some code into a directory and you're just running it from there, pip doesn't know it exists, pip didn't compile it, and so you have to compile it yourself. And so you can use the compile module
19:41
that comes with Python to compile the code to bytecode as part of your Docker packaging, and then startup will be faster. And again, there's plenty of other best practices from signal handling for shutdown to health checks. If you want to learn about signal handling for shutdowns,
20:01
Henick, it's pronounced his name, Henick has a nice article about this. And so at this point, you have a Docker image that is correct in terms of how it runs, but not necessarily correct in how you build it.
20:22
And so if it's been like, you spent the past there too, among other things, fixing bugs in your code and going to meetings, but also doing Docker packaging. Over the course of two days, the things you depend on, like the Linux distribution you're using for your base image, version of Python, Django, NumPy,
20:43
whatever libraries you use, there's probably not going to be a major release. And so if you're saying, just install the latest version of everything, that's fine. Like if you do it today and do it yesterday, if you did it yesterday, and then you'll get the same image more or less most of the time at least. Six months from now,
21:00
if you try to rebuild an image that installs the latest version of everything, some of those dependencies will have changed. If you try to rebuild it two years later, all of them will have changed. And so the problem here is if you're always installing the latest version of the code, you might go back to something that hasn't changed in six months. You just want to do a minor bug fix and you rebuild the image
21:20
and suddenly three major dependencies have changed and you've broken everything, even though all you want to do is a major bug fix. So over time, once you have a Docker file that you're using over time, you want to make sure that it's reproducible. You want to make sure that you're installing specific versions of specific packages, physical Linux distribution.
21:40
So when you rebuild the image, you'll get the exact same image. Which isn't to say you shouldn't be doing updates. You should, but you should be doing those in a controlled manner, not as a side effect of doing the minor bug fix. So one example of the ways you should make your image reproducible is by choosing a good base image. So Docker images are typically based
22:01
on some other Docker image. You use the from command at the beginning to say, use this as my base image. And typically they're based on some Linux distribution. And so you want a Linux distribution that will guarantee things like security updates, will also guarantee stability for some period of time. So like two, three years of guaranteeing bug fixes
22:22
while not changing ABIs, major version libraries, that sort of thing. Like just that you want the Linux distribution to be stable, not change out from under you unexpectedly. And so go into long-term support, Debian stable or CentOS are all Linux distributions that make the guarantee. The official Python Docker images are based on Debian stable by default,
22:43
but they also give you access to different versions of Python, not just the versions of Debian stable happens to have. So when Python 3.9 comes out, Debian stable won't have it, but the official Python image will just take Debian stable and add Python 3.9 to it. So I like using the official Python images. So for example, Python 3.9 slim buster
23:02
is Python 3.8 the latest point release. So 3.8.4, if that's the latest release, 3.8.5, if that's the latest point release, on Debian buster, which is the latest version of Debian stable, slim means a smaller version because there's like a smaller version, the bigger version, the bigger version just takes more disk space, but it has more debugging utilities.
23:24
And if you use a stable base image, you'll have more reproducible builds. Again, lots of other things you need to do, like pinning your Python packages. Once you have reproducible builds, your builds are correct, your runtime is correct,
23:40
in some sense you're done. But at that point, you might want to start thinking about some optimizations. It's correct, but you might be able to make things more efficient. A good starting point is faster builds because your time is expensive. And if every time you do a build, it takes 30 minutes to build your Docker image, you can't see if your tests are passing
24:01
until that build passes. It's just slowing you down, wasting your time, wasting your teammates' time. So it's worth spending some time optimizing build times. And one best practice for making faster builds is avoid using Alpine Linux. Alpine Linux is a Linux distribution and it's a small Linux distribution, makes for smaller images.
24:21
And so it's often recommended as a base image for Docker images. If you're a Go programmer, that's fine advice. If you're a Python programmer, you should not use it as your base image. The issue is that if you're a package maintainer who uploads packages to PyPI, you can upload pre-compiled binaries like Linux, Mac OS, Windows,
24:41
and then someone who downloads that binary doesn't have to compile the C code in the package. Lots of Python packages, you have lots of C code. And so not having to compile the packages saves lots of time installing them. Alpine cannot use binary wheels from PyPI these days at least. It might change in the future. So just to compare,
25:01
if you install pandas and matlab on my computer, if you use the Debian-based official image, Python 3.8, it installs in 30 seconds. It just downloads it, unpacks it, it's done. If you're using the Alpine variant, it takes 1500 seconds. It's 50 times slower because it has to compile a whole pile of C code. It has to install a compiler,
25:21
a compiler toolchain. It's just much, much slower. So if you want fast builds, don't use Alpine Linux. And then plenty of other best practices. The final step is making your image smaller. Having a 2 gigabyte image is waste bandwidth, waste time. It might be worth optimizing, that part.
25:42
One example of best practice out of many is typically when pip install something, let's say pip install pandas or Django, it downloads the Django package, unzips it or untires it and then keeps that package around. So that if you pip install later, it won't have to download it again. In a Docker image,
26:00
you're never going to run pip install again. So keeping this extra copy of the package around just wastes space. So if you add the no cache there option to pip install, you'll end up with a smaller Docker image and then with no harm done because you're never going to run pip install again. And again, plenty of other best practices.
26:20
To recap, you start getting something working, make it secure, make it automated, make things easy to identify and debug and easier to run better, make builds reproducible and then optimize with faster builds and smaller images. The goal here is to have some good stopping points. If you do security first, if you stop right after doing security, at least you have a secure image.
26:41
If you mix up security with making our images smaller, you might have a half secure image, which isn't ideal if you're forced to stop. And again, your particular application environment might result in different priorities. So this is just a suggested starting point for how you should, the order and what you should work on your Docker image. Your application might be different,
27:01
but this is, I think it's a reasonable starting point and a reasonable way to remember all the different things that go into it. So thanks for coming to my talk. As I said, many of these best practices are covered in great detail on the free guide on my website. And there's links to that guide and other resources for Python macro packaging as well.
27:22
These, as well as these slides at pythonspeed.com slash europython2020. Here's my email and Twitter account. If you have any questions, I believe there's a talk channel in Discord for this talk, hash talk dash docker dash packaging. And we might have time for a question or two.
27:42
Yeah, thank you very much for the talk first. And there's a few questions. And the first one is, can you give an example for install dependencies separately from your code as in your best practice? Yes, so the way Docker packaging works is install things in layers.
28:05
So each line in your Docker file to first approximation is a layer. And Docker has this caching system where when you rebuild an image, you'll say, if this layer hasn't changed, I don't have to rebuild it. And the way it decides if it's changed
28:21
is based on either the text of the command or the files you copied in. And so if you install both your code and your dependencies together, that means you have to copy in everything and then install your dependencies and pandas and Nepal and Django and Flask and what have you and your code at the same time.
28:43
And so if you change your source code, that's going to invalidate the cache and you're going to have to rebuild, you'll have to reinstall all your dependencies. So even though like you're still installing the exact same packages, you're still installing the exact same version of Django and exact same version of your Postgres adapter and so on,
29:04
you're still going to have to, you can't use the cache, you're going to have to redo that from scratch. If, however, instead of copying all the files and installing things together, you first copy in requirements.txt and then do a run pip install minus our requirements.txt.
29:21
Then the caching layer can say, oh, requirements.txt hasn't changed so I can just reuse this layer and then your build will be faster because you won't have to reinstall those packages every time your source code changes. Only you have to reinstall those packages when requirements.txt changes. Okay, there's a question about the compile all idea.
29:44
How does Python minus M compile all interact with Python code that uses the dunder mainpy? That's from Gus. He says we often have tools like, run like Python minus M my tool arguments. So how would that compile all work there?
30:03
I believe compile, and I could be wrong, but my understanding of how compile all works is it just finds all .py files, parses them and writes out the bytecode. So it's not running them, it's just parsing them. So it's just a file system operation, it just finds all .py files. So it doesn't matter how you run the code,
30:22
it just matters what files you have in the file system. If you pip installed your code, you typically don't need it because pip will, by default, compile things for you. Okay, thank you. There's the final question. What kind of security testing would you recommend for Docker images? Any good tools, packages?
30:42
There's a bunch of security scanners. There's Bandit, which is a security scanner for source code, for Python source codes. You'll find things like SQL injections and use of pickle. There's a tool called, command line tool called Safety that will scan for insecure Python dependencies,
31:04
although you have to pay them if you want more than, it's a commercial tool, so by default, you only get the up to one month, last month of updates. The updates can be as much as a month out of date, so you have to pay them if you want more timely security updates. There's a tool called Trivy, T-R-I-V-Y,
31:20
which will do scans on your system packages. Again, if you go to my website, to the area about Docker packaging, I have an article about security scanners for Docker packages. Okay, thank you very much.
31:40
So, at this point, it was very useful and we have to thank you for all these tips that we can use in our real life. So, here's a round of virtual applause for you and I hope you're going to find people applaud in the Discord talk channel as well.