Is that possible without any hack?
Yes, it is possible. While Docker is often used for microservice-oriented images (i.e., one single service per image), nothing keeps you from converting a monolithic old application to Docker by building a large image, and running arbitrarily many processes inside.
While it is true that a Docker image is not a standalone VM, it still contains everything an equivalent VM would contain - excluding the kernel. I.e., all OS libraries (libc, everything else...) are in there anyways.
If yes, what are the big lines to follow?
A Dockerfile is just a funky version of a plain old shell script which you could ordinarily use to provision your software with all its dependencies. That is, you chain a lot of commands, which are executed during the build process, and whatever ends up in the file system at the end gets packed up into an image.
So the general route is:
- Pick a base image. Depending on your needs and security requirements, you can pick your favourite distribution from Dockerhub. At first, you can pick any Debian, Ubuntu or whatever distribution you prefer and are used to. See below for further considerations.
- Those base images usually are pretty small. Hence, your next step is to use
RUN apt install... (or whatever package manager your base image uses) to add any standard packages you like.
- Finally, you have to install your own software somehow. If you follow the usual approach, you would prepare any binaries and other files you need in the host-side directory alongside your Dockerfile, and then
ADD them in.
- The last step is to (optionally) create a
ENTRYPOINT, i.e., a command that is run when the image is started up, as well as
EXPOSE routing information, run as non-root
USER or set
These are the big lines. If you just do it naively, you will end up with a huge image (gigabytes), and create a new huge image everytime you rebuild, wich can pose serious problems. The next steps are thus:
- Switch the base image to a smaller one.
alpine is good.
- For each
RUNcall, make sure there are no temporary/unneccessary files left afterwards. For example,
apt install leaves you with a lot of cache junk in
/var (same for the other package managers). Due to the way Docker works with its onion-style layered file systems, you have to get rid of the junk within the same
RUN command, you cannot clean up in a second
RUN. If you google, you will find plenty of examples.
- You probably pulled in too many packages with your package manager, fix that.
- For installing your own software, the same applies. Avoid having temporary
.tar archives lying around which you don't need anymore.
These steps should significantly reduce your image size already. Depending on what you are actually doing, 500'ish MB is a good target to avoid over-optimizing at this stage.
The final step is to optimize everything:
- Make sure that you do not have to rebuild every single intermediate image each time, by ordering the lines in the Dockerfile appropriately. You can get away with large images if the "largeness" happens in the first few image layers, if those change seldom, and can be re-used by many final images.
- For an intermediate image to be re-used, it must be bitwise identical to another one. So be sure to fix all variable information (like version numbers etc.) as best you can.
- Obviously, if you get the hang of all of this, and get your complex, many-part application running inside a single Docker image, then it's time to split it apart across multiple images (rinse, repeat). Anything that communicates by TCP/IP should be an easy choice to do so.