We recently introduced the levv exec
command as part of our ongoing effort to simplify running containers in production. This command is particularly valuable for inspecting and debugging running containers. In this tutorial, we’ll delve into common use cases for the exec
command and highlight best practices to follow and anti-patterns to avoid.
Note: This tutorial examples use the
levv exec
command, but the same applies todocker exec
.
Using the levv exec
Command
Executing a Command in a running Container
Executing a command in a running container is straightforward:
Alternatively, you can also allocate a pseudo-tty using the --tty
or -t
option. This allows you to see the formatted output as you would if you were directly inside the container, rather than just receiving the output as plain text.
Executing a Command in the Background
If you want to execute a long-running command in your container (watch out for anti-patterns), you can do so by using the -d
/--detach
option.
$ levv exec -d my-svc curl <https://mirror.accum.se/mirror/wikimedia.org/dumps/>
This will execute the command in your container without blocking your active terminal window.
Running an Interactive Shell in a Container
Sometimes, you may need a more hands-on approach to interact with your container. You can start an interactive shell by using the -i
/--interactive
option.
$ levv exec -it my-service sh
/ # ls
bin data dev etc home proc root sys tmp usr var www
/ # echo "Running in a shell inside my container"
This opens a shell in the specified container, allowing you to execute multiple commands interactively.
You’ll notice we also used the -t
/--tty
option. When using -i
/--interactive
, we always allocate a pseudo terminal. It will work without it but the interactivity will be less intuitive (go ahead and try it!).
Anti-Patterns
The exec
command is powerful and practical as it makes it easy to interact with your containers. However, it needs to be used wisely or it might make you lose key benefits of containers like reproducibility.
Here we present the main exec
anti-patterns.
Directly modifying Application State
One of the main advantage of containers is their reproducibility and predictability. When using exec
to change an application configuration file or source code, we effectively lose those advantages. We expose ourselves to unplanned behavior and make it harder to debug in case of issue.
This is sometimes acceptable for development or staging applications to speed up the development cycle, but it should never be done in a production application.
Executing long-running Processes
Using exec
to execute long processes is a major anti-pattern, as it can lead to resource contention and negatively impact your main application. For instance, instead of running a database dump using exec
, you should use a shared volume and a separate container.
The same applies here: tolerated in dev/staging, never in production.
Relying on exec
in your Production Workloads
You probably understood this from the previous anti-patterns: do not rely on exec
for your production workloads.
Over-reliance on the exec
command for making changes (like hot-fixes or worse changes in application state) in production environments can lead to unexpected behavior and challenges in maintaining consistency across your containers. You will lose de facto the scalability and predictability offered by containers.
Best Practices
Now that you know what to avoid, in this section we cover how to make the best use of exec
.
But first, let’s examine security and the concept of immutability.
exec
and Security
While the exec
command is handy, it poses security risks. An attacker could exploit the command to execute malicious code within your container.
Another category of security risk is that you could inadvertently expose sensitive data in your output when executing commands in your container.
exec
and Immutability
Immutability characterizes systems or components that cannot be modified after their creation. For containers, this means that instead of modifying it, we build a new image and re-deploy it entirely.
When working with containers in production, immutability is a good practice as it comes with operational benefits:
- no configuration drift
- change history
- predictability
and security benefits:
- easy auditing
- reduced attack surface
- integrity
Understanding the security and operational implications of using exec
allows for more mature usage of the command.
On to the best practices:
Best Practice 1 : Use Specific Commands
Execute specific commands when interacting with your containers instead of opening interactive shells by default. It minimizes risk and ensures clarity in your needs when interacting with your container.
Best Practice 2 : Make Your Production Containers Immutable
Containers are not immutable by default. You need to actually make them so. Once your application is ready for production, you can update your Dockerfile to make your image immutable:
- Don’t run as root: use the
USER
instruction - Remove shell access by deleting all binaries:
RUN rm -rf /bin/*
You can also go one-step further by making your container filesystem read-only. This does not happen in the Dockerfile definition but at run time with the --read-only
flag of docker run
or with by setting read-only: true
in your compose configuration.
Note: At the time of writing, levv does not yet support the
read-only
keyword.
If you make your filesystem read-only you will need to mount volumes at the path where your application needs to write under normal conditions.
With this article, we covered example use cases of the exec
command, some anti-patterns to avoid when working with exec
and security and operation best practices.