Container stuck on boot? How to read systemd logs in Docker
Learn how to troubleshoot container startup failures using journald and deep background process inspection when systemd freezes.
Summary
- The systemd init system acts as a service manager inside the operating system, orchestrating critical startup routines.
- Containers running systemd internally require special mount privileges to interact correctly with the host kernel.
- The journald logging daemon captures structured logs that allow centralized auditing of container boot failures.
- Deep inspection commands help identify dead or hanging services before the container drops its runtime logs.
- Proper volume mapping and environment variables prevent the main process from exiting prematurely during startup.
Why containers freeze during boot and the role of systemd
When a Docker container starts, the default behavior is to execute a single main process, often referred to as PID 1 (the first process in the operating system). However, in scenarios where we need to run multiple interdependent services inside the same container—such as web servers, local databases, and scheduled tasks—we rely on systemd, which is the software responsible for initializing, managing, and monitoring all Linux system services in an organized way.
The problem is that systemd was designed to run directly on bare metal hardware or inside full virtual machines, rather than inside an isolated container environment. When we try to initialize it without the proper permissions, it loses access to vital kernel resources (the core of the operating system that manages hardware), resulting in silent freezes right at boot time. In practice, this means the container appears to run, but no internal services respond and the screen stays frozen during initialization.
To thoroughly understand how to diagnose and resolve this type of failure, we need to analyze the internals of the startup sequence and see how the logging subsystem behaves. If you already manage complex environments, it is also worth checking our tips on how to resolve Docker pipeline failures caused by low disk space, as storage issues frequently corrupt service boot caches.
Preparing Docker to run systemd securely
For systemd to function inside a Docker container, we must grant it specific system permissions that are normally blocked for security reasons. The primary design decision is to avoid the indiscriminate use of full privileged mode, which exposes the host to severe vulnerabilities, by using granular mount flags and directory mappings instead.
Systemd requires read-write access to the kernel's virtual file system located at /sys/fs/cgroup, along with a mechanism to manage process signals. When starting the container, we must map the host cgroups volume and ensure the startup command points directly to the systemd binary rather than a standard shell script that would terminate PID 1 incorrectly.
Below is a functional example of a Docker Compose file configured correctly to allow systemd to initialize without kernel permission errors:
version: '3.8'&#nservices:&#n systemd-app:&#n build: .&#n container_name: systemd_boot_debug&#n privileged: true&#n volumes:&#n - /sys/fs/cgroup:/sys/fs/cgroup:rw&#n command: [/usr/sbin/init]&#nCapturing and reading journald logs inside the container
When systemd freezes during startup, Docker usually cannot display useful messages in the docker logs command because the container's standard output (stdout) is not receiving the streams generated by the sub-services managed by systemd. This is where journald comes in, which is the systemd component responsible for collecting, indexing, and storing text and binary logs from the entire operating system.
To inspect these logs in real-time or investigate what happened right after boot, we need to access the running container's internal terminal using command-line tools. The journalctl utility is the key to filtering events and discovering exactly which service failed to start and why the process stalled.
You can run the command below to list critical errors that occurred since the container's last startup:
docker exec -it systemd_boot_debug journalctl -b 0 -p err&#nThis command filters the journald log record (journalctl) by specifying the current boot (-b 0) and displaying only messages with an error severity level (-p err). If there are failures in network daemons or permission denials in configuration files, they will appear immediately on the screen.
Tracking background processes and hidden failures
Often, a boot freeze does not generate an explicit error in journald, but rather an infinite loop where a dependent process tries to start before the file system is fully mounted. To map this background process tree, we resort to deep inspection commands of the container's internal state, such as systemctl adapted for isolated environments.
By executing the general service status command, we can identify units marked with the failed or deactivating state. This direct visual diagnostic avoids guesswork and points out the exact bottleneck preventing the container from finishing its initialization.
Run the command below in your terminal to inspect all active and failed units in systemd:
docker exec -it systemd_boot_debug systemctl list-units --state=failed&#nIf the container is suffering from intermittent internal connectivity drops during boot due to firewall rules applied by the daemon itself, it can be useful to correlate the issue with network traffic analysis, as explained in our article on how to debug Docker container network failures with tcpdump. To explore these architectural concepts further in production, see our related technical analysis on How to monitor pod network latency in Kubernetes with eBPF.
Best practices to prevent boot freezes in production
Ensuring that containers running systemd initialize deterministically requires careful planning in image builds and entrypoint scripts. A common trap is trying to execute commands that require active networking before the loopback interface or internal DNS resolver is ready.
To mitigate these risks, always use explicit dependencies in systemd units through the After= and Requires= directives, ensuring a service is only triggered when its prerequisites are 100% operational. Additionally, keep logs centralized and perform local load tests using monitoring tools to validate boot response times before promoting changes to production environments.
In summary, debugging the boot sequence of systemd-controlled containers stops being a mystery when we combine proper cgroup mapping, accurate reading of journald records, and rigorous inspection of unit states. Mastering these techniques ensures greater resilience for legacy or monolithic applications that rely on traditional service managers within the Docker ecosystem.