Container Troubleshooting
Complete the full lesson to earn 25 points
Work through each section, then tap “Mark as Complete” on the last one.
Module: Troubleshooting
Section: Application and Database Issues
Lesson: Container Troubleshooting
Introduction: The Reality of Containerized Environments
In modern software architecture, containers have become the standard unit for deploying applications. By packaging code, runtime, system tools, and libraries into a single executable unit, containers promise consistency across development, testing, and production environments. However, this abstraction layer introduces a new set of complexities when things inevitably go wrong. Unlike traditional virtual machines where you might have persistent SSH access and a familiar OS environment, containers are often ephemeral, immutable, and isolated.
Troubleshooting containerized applications requires a shift in mindset. You are no longer looking for a "broken server" in the traditional sense; you are investigating an isolated environment that may have ceased to exist seconds after an error occurred. When a container crashes, fails to start, or exhibits high latency, the symptoms often hide in the layers of the container image, the orchestration platform, or the underlying network configuration. Understanding how to peel back these layers—from the container runtime to the application logs—is a critical skill for any engineer managing modern infrastructure.
This lesson explores the systematic approach to diagnosing and resolving issues in containerized environments. We will move beyond simply restarting pods or containers and look at the underlying causes, the tools at your disposal, and the best practices to ensure your containers remain observable and manageable.
Understanding the Lifecycle of a Container Failure
To troubleshoot effectively, you must first categorize the failure. Is the issue occurring during the build process, the deployment phase, or while the application is running? Most container failures fall into one of four buckets:
- Image and Registry Issues: The container fails to start because the image is missing, corrupted, or incompatible with the host architecture.
- Configuration and Environment Issues: The container starts, but the application crashes immediately due to missing environment variables, incorrect secrets, or invalid configuration files.
- Resource Contention: The container is killed by the host kernel (often due to Out-Of-Memory events) or is throttled because of aggressive resource limits.
- Application-Level Errors: The container is running, but the application inside is failing, throwing internal errors, or failing health checks.
Callout: The Ephemeral Nature of Containers It is vital to remember that containers are designed to be disposable. If you treat a container like a long-lived server—manually patching files inside it or editing configurations via shell access—you are fighting against the fundamental design of containerization. Always fix the source image or the deployment manifest, not the running container instance.
Section 1: Investigating Start-up Failures
When a container refuses to start, the most common culprit is an issue with the entrypoint script or a missing dependency. If you are using Docker, the docker run command will exit immediately. If you are using Kubernetes, the pod will enter a CrashLoopBackOff state.
Analyzing Logs and Events
The first step is always to inspect the logs. Even if the container crashed immediately, the logs often capture the final milliseconds of execution.
- Docker: Use
docker logs <container_id>to see the standard output (stdout) and standard error (stderr). If the container exited, the logs remain available until the container is removed. - Kubernetes: Use
kubectl logs <pod_name>orkubectl logs <pod_name> -p(the-pflag stands for "previous" and is invaluable for accessing logs from a crashed container).
Inspecting the Container State
If the logs are empty, the container might have failed at the runtime level before the application even initialized. You can inspect the container's metadata to see why it stopped.
# Get detailed information about a container
docker inspect <container_id>
Look for the State section in the output. Key fields to investigate include:
ExitCode: An exit code of0means the process finished successfully. Any other number indicates an error. For example,137usually signifies an Out-Of-Memory (OOM) kill, and127often means the command or binary was not found.Error: This field may contain a specific string explaining why the container failed to initialize.
Note: A
127exit code is a classic sign that yourENTRYPOINTorCMDdirective points to a file that does not exist inside the container image. Always verify your working directory (WORKDIR) and path variables.
Section 2: Solving Resource Contention
Resource limits are a double-edged sword. While they prevent a single rogue container from consuming all host resources, they can also cause silent failures if configured too tightly.
The OOM Kill
The most common resource issue is the Out-Of-Memory (OOM) kill. The Linux kernel identifies a process using more memory than allowed by the control group (cgroup) and terminates it to protect the host.
To verify if your container was OOM killed:
- Check the container status:
docker inspect <container_id>. - Look for
OOMKilled: true. - Check system logs on the host:
dmesg | grep -i oomorjournalctl -k | grep -i oom.
CPU Throttling
CPU issues are often harder to detect because they don't cause the container to crash. Instead, the application becomes sluggish. If you have set a cpu_limit, the kernel will throttle the process if it exceeds its quota, leading to increased latency.
To monitor CPU usage in real-time, use:
docker stats
This command provides a live view of CPU, memory, and network I/O usage for all running containers. If you notice a container consistently hitting its CPU limit, you may need to increase the limit or optimize the application code to be more efficient.
Section 3: Network Troubleshooting inside Containers
Containers communicate over virtual networks. When a container cannot reach a database or another service, the issue is often a mismatch between local configuration and network policy.
Testing Connectivity
If your application cannot connect to an external service, start by testing basic connectivity from inside the container. If your container image is minimal (e.g., Alpine or Distroless), it might lack tools like curl, ping, or dig.
Tip: If you are using Kubernetes, consider using an "ephemeral debug container" to attach a sidecar with debugging tools to a running pod without modifying the application image.
# Example: Adding a busybox container to a running pod for debugging
kubectl debug -it <pod_name> --image=busybox --target=<container_name>
Once inside, verify the following:
- DNS Resolution: Can the container resolve the service name? Try
nslookup <service_name>. - Port Access: Is the target port open? Try
nc -zv <host> <port>. - Environment Variables: Does the application have the correct database credentials and hostnames injected via environment variables?
Common Pitfalls with Networking
- Firewall Rules: In cloud environments, security groups or network policies might block traffic between your app container and your database.
- Service Mesh Interception: If you are using a service mesh like Istio or Linkerd, traffic is routed through a sidecar proxy. If the sidecar is misconfigured, it can drop traffic even if the application code is perfect.
Section 4: Database Connection Issues
When an application fails to connect to a database, the problem usually stems from authentication, network reachability, or connection pool exhaustion.
Authentication and Secrets
Most containerized apps retrieve credentials from environment variables or mounted secret files. A common mistake is a mismatch between the expected variable name (e.g., DB_PASSWORD vs DATABASE_PASSWORD).
- Validation Step: Print the environment variables inside the container to ensure they match what the application expects. In Linux, use
env. - Secret Rotation: If you are using a secret management system, ensure the application is configured to pick up new secrets if they are rotated.
Connection Pooling
If your application throws "Too many connections" errors, you are likely hitting the database's maximum connection limit. This often happens because the application is not properly closing connections or because you have too many container replicas all attempting to maintain independent connection pools.
Callout: Connection Pooling Strategy If you have multiple application replicas, each with its own connection pool, you can quickly exhaust the database's connection limit. Consider using a dedicated database proxy (like PgBouncer for PostgreSQL) to manage and multiplex connections efficiently.
Section 5: Best Practices for Observability
Troubleshooting is significantly easier when you have the right data. If your container is a "black box," you will spend hours hunting for clues that should have been logged.
1. Structured Logging
Do not log raw strings. Use structured formats like JSON. This allows log aggregators (like ELK, Splunk, or CloudWatch) to index fields, making it trivial to search for specific error codes or user IDs across millions of log entries.
2. Health Checks (Liveness and Readiness)
Implement robust health checks.
- Liveness Probes: Tell the orchestrator if the app is alive. If it fails, the container restarts. Use this for deadlocks or unresponsive states.
- Readiness Probes: Tell the orchestrator if the app is ready to receive traffic. If it fails, the container is removed from the load balancer rotation. Use this during database migrations or slow startup periods.
3. Log Aggregation
Never rely on docker logs for production environments. Ensure your container logs are shipped to a centralized system. If a container crashes or is deleted, you lose the local logs forever. Centralization is non-negotiable for distributed systems.
4. Avoiding "Fat" Images
Keep your images as small as possible. Use multi-stage builds to separate the build environment from the runtime environment. Smaller images are faster to pull, have fewer security vulnerabilities, and are easier to debug because they contain fewer unnecessary tools.
Quick Reference: Common Exit Codes
| Exit Code | Meaning | Common Cause |
|---|---|---|
| 1 | General Application Error | Uncaught exception or script error. |
| 127 | Command Not Found | Missing binary or incorrect ENTRYPOINT. |
| 137 | OOM Kill | Memory limit reached and killed by kernel. |
| 139 | Segmentation Fault | Incompatible binary or memory corruption. |
| 143 | SIGTERM | Container received a termination signal (planned shutdown). |
Step-by-Step: Diagnosing a "CrashLoopBackOff"
If you encounter a CrashLoopBackOff in Kubernetes, follow this systematic workflow:
- Check Pod Status: Run
kubectl describe pod <name>. Look at the "Events" section at the bottom. This will tell you if the pod is failing because of a liveness probe failure or an image pull error. - Inspect Previous Logs: Use
kubectl logs <name> --previous. This is the single most useful command for debugging crashes. - Check Resource Usage: Use
kubectl top pod <name>to see if the pod is hitting memory or CPU limits immediately upon startup. - Validate Environment: Run a shell inside the container (if it stays up long enough) or use a temporary container to verify that environment variables and secrets are correctly mounted.
- Local Reproduction: Pull the exact image version locally and try to run it with
docker run. Attempt to mimic the production environment variables and volume mounts. If it fails locally with the same error, you have a configuration or code issue, not a platform issue.
Common Pitfalls and How to Avoid Them
The "It Works on My Machine" Trap
This is the most dangerous phrase in container troubleshooting. It usually happens because your local environment has different environment variables, a different Docker engine version, or different network access than the production cluster.
- Solution: Use tools like
docker-composeto standardize the local development environment, ensuring that the same configuration files are used locally as in production.
Ignoring Timeouts
Applications often fail because they are waiting for a dependency that isn't ready. If your app starts and immediately tries to connect to a database that is still initializing, the app might crash.
- Solution: Implement "wait-for-it" scripts or use orchestrator-native features (like Kubernetes
initContainers) to ensure dependencies are available before the main application process starts.
Hardcoding Configurations
Hardcoding database URLs or API keys inside the image is a major mistake. It makes the image non-portable and forces a full rebuild just to change a configuration value.
- Solution: Always inject configuration via environment variables or configuration maps. Keep the image environment-agnostic.
Summary and Key Takeaways
Troubleshooting containers requires a disciplined approach. By understanding the container lifecycle, monitoring resources effectively, and maintaining high observability, you can minimize downtime and resolve issues quickly.
- Prioritize Logs: Always check both current and previous logs. If you don't have centralized logging, you are flying blind.
- Understand Exit Codes: Memorize the common exit codes (127, 137, 143) to identify if the issue is with your code, your configuration, or the host kernel.
- Use Probes: Distinguish between Liveness and Readiness probes. Misconfiguring these is a leading cause of traffic being sent to unhealthy containers.
- Externalize Configuration: Never bake secrets or environment-specific settings into your images. Use environment variables or secret management services.
- Reproduce Locally: Always attempt to replicate the failure in a controlled local environment using the same image version and configuration.
- Resource Management: Monitor memory and CPU usage regularly. Use
docker statsorkubectl topto identify containers that are consistently hitting their limits before they cause an outage. - Keep it Simple: Small, purpose-built containers are easier to debug than large, monolithic images. If a container is hard to debug, it is often a sign that it is doing too much.
Troubleshooting is an iterative process of elimination. By systematically checking the environment, the network, the resource limits, and the application logs, you can solve almost any container-related issue. The goal is not just to fix the immediate problem, but to improve your observability so that the next time an issue arises, the answer is already waiting for you in your monitoring dashboard.
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles your daily points limit so you progress twice as fast, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× daily points limit
- ✓ Distraction-free lessons