<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Reverse Proxy | John Nessime</title>
	<atom:link href="https://john-nessime.com/blog/tag/reverse-proxy/feed/" rel="self" type="application/rss+xml" />
	<link>https://john-nessime.com/blog/tag/reverse-proxy/</link>
	<description>Cloud, DevOps, Data &#38; AI — Built, Tested, Explained</description>
	<lastBuildDate>Wed, 15 Jul 2026 17:05:32 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.2</generator>

<image>
	<url>https://john-nessime.com/blog/wp-content/uploads/2026/07/cropped-jn-32x32.png</url>
	<title>Reverse Proxy | John Nessime</title>
	<link>https://john-nessime.com/blog/tag/reverse-proxy/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Docker Issues &#038; Troubleshooting: Real-World Solutions</title>
		<link>https://john-nessime.com/blog/devops/docker-issues-troubleshooting-real-world-solutions/</link>
					<comments>https://john-nessime.com/blog/devops/docker-issues-troubleshooting-real-world-solutions/#respond</comments>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Wed, 15 Jul 2026 17:05:30 +0000</pubDate>
				<category><![CDATA[Articles]]></category>
		<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Containers]]></category>
		<category><![CDATA[Docker Compose]]></category>
		<category><![CDATA[Grafana]]></category>
		<category><![CDATA[Infrastructure]]></category>
		<category><![CDATA[Logs]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[Networking]]></category>
		<category><![CDATA[Nginx]]></category>
		<category><![CDATA[Production]]></category>
		<category><![CDATA[Prometheus]]></category>
		<category><![CDATA[Reverse Proxy]]></category>
		<category><![CDATA[Self Hosting]]></category>
		<category><![CDATA[System Administration]]></category>
		<category><![CDATA[Troubleshooting]]></category>
		<category><![CDATA[Volumes]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=10</guid>

					<description><![CDATA[<p>Docker containers rarely fail without leaving clues. This practical guide explores common Docker issues involving networking, storage, permissions, ports, logs, health checks, reverse proxies, and resource usage—and explains how to troubleshoot them methodically.</p>
<p>The post <a href="https://john-nessime.com/blog/devops/docker-issues-troubleshooting-real-world-solutions/">Docker Issues &amp; Troubleshooting: Real-World Solutions</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[

<p class="article-introduction wp-block-paragraph">Docker feels almost magical when you first start using it. You write a configuration file, run one command, and an entire application stack appears.</p>



<pre class="wp-block-code"><code>docker compose up -d</code></pre>



<p class="wp-block-paragraph">A web application, database, cache, monitoring service, and reverse proxy can all be launched in seconds. Everything is isolated, repeatable, and relatively easy to move between environments.</p>



<p class="wp-block-paragraph">Then, eventually, something breaks.</p>



<p class="wp-block-paragraph">A container refuses to start. A port is already occupied. A database loses access to its storage directory. One service cannot resolve the hostname of another. A reverse proxy returns a 502 error even though the application appears to be running.</p>



<p class="wp-block-paragraph">That is usually the point where Docker stops feeling like a convenient deployment tool and starts becoming a real infrastructure skill.</p>



<p class="wp-block-paragraph">This guide is based on the way I approach Docker issues on real Linux servers. It is not a list of commands to copy blindly. It is a troubleshooting method for discovering what failed, understanding why it failed, and solving it without making the situation worse.</p>



<h2 class="wp-block-heading">Docker Is Usually Not the Real Problem</h2>



<p class="wp-block-paragraph">When a container fails, Docker is often blamed first. In many cases, however, Docker is only exposing a problem somewhere else in the system.</p>



<ul class="wp-block-list">
<li>Incorrect Linux file permissions</li>
<li>A missing environment variable</li>
<li>An unavailable database</li>
<li>A port conflict on the host</li>
<li>An incorrect volume mount</li>
<li>A DNS or network configuration problem</li>
<li>A reverse proxy pointing to the wrong destination</li>
<li>Insufficient memory or disk space</li>
<li>An application-level configuration error</li>
</ul>



<p class="wp-block-paragraph">The container runtime may be working exactly as designed. The application inside the container may simply be unable to operate under the conditions it was given.</p>



<p class="wp-block-paragraph">This distinction matters because repeatedly deleting and rebuilding containers rarely fixes the underlying issue. It may temporarily hide it, but the same failure usually returns.</p>



<h2 class="wp-block-heading">My First Rule of Docker Troubleshooting: Do Not Delete Anything Yet</h2>



<p class="wp-block-paragraph">When a container suddenly exits, the first instinct is often to remove it, rebuild the image, or restart the entire stack. That destroys useful evidence.</p>



<p class="wp-block-paragraph">Before changing anything, I start by collecting information.</p>



<pre class="wp-block-code"><code>docker ps -a</code></pre>



<p class="wp-block-paragraph">This shows running and stopped containers, their current states, exposed ports, names, and recent exit information.</p>



<pre class="wp-block-code"><code>docker logs container-name</code></pre>



<p class="wp-block-paragraph">The logs frequently reveal the immediate cause: a missing file, invalid password, failed database connection, permission error, or malformed configuration.</p>



<pre class="wp-block-code"><code>docker inspect container-name</code></pre>



<p class="wp-block-paragraph">Inspection provides the container&#8217;s network settings, environment variables, volume mounts, restart policy, health status, image information, and runtime configuration.</p>



<p class="wp-block-paragraph">These three commands answer a large percentage of Docker troubleshooting questions before any corrective action is required.</p>



<h2 class="wp-block-heading">Understanding Docker Container States</h2>



<p class="wp-block-paragraph">A container can exist in several states, and each state tells a different story.</p>



<h3 class="wp-block-heading">Running</h3>



<p class="wp-block-paragraph">The main process inside the container is active. This does not automatically mean the application is healthy or ready to receive traffic.</p>



<h3 class="wp-block-heading">Exited</h3>



<p class="wp-block-paragraph">The container&#8217;s main process stopped. It may have completed normally, crashed, or failed during startup. The exit code and logs provide the next clues.</p>



<h3 class="wp-block-heading">Restarting</h3>



<p class="wp-block-paragraph">The container repeatedly starts and fails while Docker applies its restart policy. This commonly indicates an application crash, invalid configuration, inaccessible dependency, or permission problem.</p>



<h3 class="wp-block-heading">Created</h3>



<p class="wp-block-paragraph">The container was created but its main process has not started successfully.</p>



<h3 class="wp-block-heading">Dead</h3>



<p class="wp-block-paragraph">Docker could not properly stop or remove the container. This is less common and may indicate a lower-level runtime, storage, or host issue.</p>



<p class="wp-block-paragraph">Knowing the state prevents random troubleshooting. A networking issue requires a different investigation from a process that exits because of a syntax error.</p>



<h2 class="wp-block-heading">Start With the Container Logs</h2>



<p class="wp-block-paragraph">Logs are normally the fastest route to the cause of a Docker issue.</p>



<pre class="wp-block-code"><code>docker logs --tail 100 container-name</code></pre>



<p class="wp-block-paragraph">This displays the latest 100 lines without flooding the terminal with the container&#8217;s entire history.</p>



<pre class="wp-block-code"><code>docker logs -f container-name</code></pre>



<p class="wp-block-paragraph">The follow option streams new log entries in real time. It is useful while restarting a service or reproducing a problem.</p>



<pre class="wp-block-code"><code>docker logs --since 30m container-name</code></pre>



<p class="wp-block-paragraph">This limits the output to a recent period, which is especially useful for long-running containers.</p>



<p class="wp-block-paragraph">When reading logs, I do not look only at the final line. The true cause often appears several lines earlier. The last error may simply be a consequence of the first failure.</p>



<h2 class="wp-block-heading">Docker Networking: Where Many Problems Begin</h2>



<p class="wp-block-paragraph">Docker networking is one of the most common sources of confusion, especially when several containers need to communicate.</p>



<p class="wp-block-paragraph">Imagine a stack containing:</p>



<ul class="wp-block-list">
<li>Grafana</li>
<li>Prometheus</li>
<li>A web application</li>
<li>MySQL or PostgreSQL</li>
<li>Redis</li>
<li>Nginx or Apache</li>
</ul>



<p class="wp-block-paragraph">Each service may work independently while communication between them fails.</p>



<h3 class="wp-block-heading">The Localhost Mistake</h3>



<p class="wp-block-paragraph">Inside a container, <code>localhost</code> refers to that container itself. It does not refer to the Docker host, and it does not refer to another container.</p>



<p class="wp-block-paragraph">For example, if Grafana needs to connect to Prometheus, the data source address should usually use the Docker Compose service name:</p>



<pre class="wp-block-code"><code>http://prometheus:9090</code></pre>



<p class="wp-block-paragraph">Using the following from inside the Grafana container would normally point back to Grafana itself:</p>



<pre class="wp-block-code"><code>http://localhost:9090</code></pre>



<p class="wp-block-paragraph">This small misunderstanding causes a surprising number of connection failures.</p>



<h3 class="wp-block-heading">Confirm That Containers Share a Network</h3>



<pre class="wp-block-code"><code>docker network ls</code></pre>



<pre class="wp-block-code"><code>docker network inspect network-name</code></pre>



<p class="wp-block-paragraph">The inspection output shows which containers are attached to a network and which IP addresses were assigned.</p>



<p class="wp-block-paragraph">Containers on unrelated networks cannot automatically communicate by service name. They must share a Docker network or use another explicitly configured route.</p>



<h3 class="wp-block-heading">Test Connectivity From Inside the Container</h3>



<pre class="wp-block-code"><code>docker exec -it container-name sh</code></pre>



<p class="wp-block-paragraph">Depending on the image, Bash may be available instead:</p>



<pre class="wp-block-code"><code>docker exec -it container-name bash</code></pre>



<p class="wp-block-paragraph">From inside the container, test name resolution and connectivity using the utilities available in that image:</p>



<pre class="wp-block-code"><code>getent hosts database
curl http://application:8080
wget -qO- http://prometheus:9090/-/ready</code></pre>



<p class="wp-block-paragraph">A successful result confirms that Docker DNS and network routing are functioning. A failed result narrows the investigation considerably.</p>



<h2 class="wp-block-heading">Host Ports and Container Ports Are Different</h2>



<p class="wp-block-paragraph">A Docker Compose port mapping such as the following is read from left to right:</p>



<pre class="wp-block-code"><code>ports:
  - "8080:80"</code></pre>



<p class="wp-block-paragraph">Port <code>8080</code> belongs to the Docker host. Port <code>80</code> belongs to the application inside the container.</p>



<p class="wp-block-paragraph">Traffic sent to the host on port 8080 is forwarded to port 80 inside the container.</p>



<p class="wp-block-paragraph">If another service already occupies host port 8080, Docker cannot bind to it.</p>



<pre class="wp-block-code"><code>ss -tulpn</code></pre>



<p class="wp-block-paragraph">To check a specific port:</p>



<pre class="wp-block-code"><code>ss -tulpn | grep :8080</code></pre>



<p class="wp-block-paragraph">A common mistake is changing the container&#8217;s internal port when only the host-side mapping needs to change.</p>



<p class="wp-block-paragraph">For example, this avoids a conflict while leaving the application untouched:</p>



<pre class="wp-block-code"><code>ports:
  - "8081:80"</code></pre>



<h2 class="wp-block-heading">Published Ports Are Not Required for Container-to-Container Traffic</h2>



<p class="wp-block-paragraph">Containers connected to the same Docker network can usually communicate through their internal ports without publishing those ports to the host.</p>



<p class="wp-block-paragraph">For example, Prometheus can communicate with an exporter on the shared Docker network without exposing that exporter publicly.</p>



<p class="wp-block-paragraph">This reduces unnecessary exposure and produces a cleaner architecture. Publish only the ports that must be reached from the host, a reverse proxy, or an external client.</p>



<h2 class="wp-block-heading">Docker Volumes: Persistent Storage Done Properly</h2>



<p class="wp-block-paragraph">Containers should be treated as disposable. Persistent data should not depend on the writable filesystem inside a container.</p>



<p class="wp-block-paragraph">Without a correctly configured volume, deleting a database container may also delete the database stored inside it.</p>



<h3 class="wp-block-heading">Named Volumes</h3>



<pre class="wp-block-code"><code>services:
  database:
    image: mariadb:latest
    volumes:
      - database-data:/var/lib/mysql

volumes:
  database-data:</code></pre>



<p class="wp-block-paragraph">Docker manages the storage location while the application writes to its expected internal directory.</p>



<h3 class="wp-block-heading">Bind Mounts</h3>



<pre class="wp-block-code"><code>volumes:
  - ./config:/etc/application</code></pre>



<p class="wp-block-paragraph">A bind mount maps a specific host path into the container. It is useful for configuration files, development directories, and data that administrators need to access directly.</p>



<p class="wp-block-paragraph">Bind mounts also introduce more responsibility. The host directory must exist with the correct permissions, ownership, security context, and contents.</p>



<h3 class="wp-block-heading">Inspect Existing Volumes</h3>



<pre class="wp-block-code"><code>docker volume ls</code></pre>



<pre class="wp-block-code"><code>docker volume inspect volume-name</code></pre>



<p class="wp-block-paragraph">Before deleting an apparently unused volume, confirm which application created it and whether it contains irreplaceable data.</p>



<h2 class="wp-block-heading">Permission Problems Are Quiet but Destructive</h2>



<p class="wp-block-paragraph">Permission errors can prevent databases from initializing, web applications from uploading files, monitoring tools from writing data, or services from reading configuration files.</p>



<p class="wp-block-paragraph">The first checks are simple:</p>



<pre class="wp-block-code"><code>ls -lah
stat path-to-file
stat path-to-directory</code></pre>



<p class="wp-block-paragraph">The user inside the container may have a different numeric user ID from the owner of the host directory. The names do not matter as much as the numeric UID and GID.</p>



<pre class="wp-block-code"><code>docker exec container-name id</code></pre>



<p class="wp-block-paragraph">Compare that result with the ownership of the mounted host path.</p>



<p class="wp-block-paragraph">Changing permissions to <code>777</code> may appear to solve the issue, but it is usually an unsafe shortcut rather than a proper solution. Correct ownership and the minimum required permissions are preferable.</p>



<h2 class="wp-block-heading">SELinux Can Affect Docker Bind Mounts</h2>



<p class="wp-block-paragraph">On distributions such as AlmaLinux, Rocky Linux, CentOS Stream, Fedora, and Red Hat Enterprise Linux, standard Unix permissions may look correct while SELinux still blocks access.</p>



<p class="wp-block-paragraph">Docker Compose bind mounts may require an SELinux relabeling option:</p>



<pre class="wp-block-code"><code>volumes:
  - ./data:/var/lib/application:Z</code></pre>



<p class="wp-block-paragraph">The uppercase <code>Z</code> assigns a private SELinux label for the container. A lowercase <code>z</code> is used when the content must be shared between multiple containers.</p>



<p class="wp-block-paragraph">SELinux should not be disabled simply to make a container work. The better approach is to identify the denied access and apply the correct labeling or policy.</p>

<br><img decoding="async" src="https://john-nessime.com/blog/wp-content/uploads/2026/07/Docker-Issues-Troubleshooting-Real-World-Solutions-2.png" alt="Docker Issues &#038; Troubleshooting - Real-World Solutions | John Nessime">
<br>

<h2 class="wp-block-heading">Environment Variables Can Break an Entire Stack</h2>



<p class="wp-block-paragraph">Modern containerized applications depend heavily on environment variables for database credentials, API keys, application URLs, encryption secrets, feature flags, and runtime settings.</p>



<p class="wp-block-paragraph">One missing or incorrectly escaped value can stop a service from starting.</p>



<pre class="wp-block-code"><code>docker compose config</code></pre>



<p class="wp-block-paragraph">This command renders the resolved Docker Compose configuration. It helps reveal missing substitutions, merged settings, malformed values, and unexpected defaults.</p>



<p class="wp-block-paragraph">To inspect the environment available inside a running container:</p>



<pre class="wp-block-code"><code>docker exec container-name env</code></pre>



<p class="wp-block-paragraph">Be careful when sharing this output because it may include passwords, tokens, and private service credentials.</p>



<h2 class="wp-block-heading">Docker Compose Configuration Errors</h2>



<p class="wp-block-paragraph">YAML is readable, but it is sensitive to indentation and structure. A service can be valid YAML while still being configured incorrectly for Docker Compose.</p>



<p class="wp-block-paragraph">Before deploying a change, validate the file:</p>



<pre class="wp-block-code"><code>docker compose config --quiet</code></pre>



<p class="wp-block-paragraph">If the command returns without an error, the configuration is structurally valid.</p>



<p class="wp-block-paragraph">For a complete rendered version:</p>



<pre class="wp-block-code"><code>docker compose config</code></pre>



<p class="wp-block-paragraph">This is especially valuable when Compose files use environment substitutions, overrides, extension fields, or multiple configuration files.</p>



<h2 class="wp-block-heading">Health Checks Reveal More Than Running Status</h2>



<p class="wp-block-paragraph">A container can be running while the application inside it is unusable.</p>



<p class="wp-block-paragraph">A database may still be initializing. A web service may be listening but unable to connect to its database. An API may be running but returning errors for every request.</p>



<p class="wp-block-paragraph">A health check lets Docker test actual application readiness.</p>



<pre class="wp-block-code"><code>healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 20s</code></pre>



<p class="wp-block-paragraph">The application should expose a lightweight endpoint that confirms its critical dependencies are available.</p>



<p class="wp-block-paragraph">To inspect the current status:</p>



<pre class="wp-block-code"><code>docker inspect --format='{{json .State.Health}}' container-name</code></pre>



<p class="wp-block-paragraph">A health check does not repair an unhealthy service by itself, but it provides a far more accurate operational signal than process status alone.</p>



<h2 class="wp-block-heading">Restart Policies Can Hide Repeated Failures</h2>



<p class="wp-block-paragraph">Restart policies are useful in production, but they can create the impression that a service is stable when it is actually crashing continuously.</p>



<pre class="wp-block-code"><code>restart: unless-stopped</code></pre>



<p class="wp-block-paragraph">If the application exits every few seconds, Docker restarts it every few seconds. The container may appear in the process list, but the service may never become ready.</p>



<p class="wp-block-paragraph">Check the restart count:</p>



<pre class="wp-block-code"><code>docker inspect --format='{{.RestartCount}}' container-name</code></pre>



<p class="wp-block-paragraph">A rapidly increasing number indicates that the restart policy is masking an unresolved failure.</p>



<h2 class="wp-block-heading">Reverse Proxy Errors: 502, 504, SSL Problems, and Redirect Loops</h2>



<p class="wp-block-paragraph">Reverse proxies such as Nginx, Apache, Traefik, and Caddy often sit between users and containerized applications.</p>



<p class="wp-block-paragraph">When the public website fails, the application container may still be perfectly healthy. The proxy may simply be unable to reach it.</p>



<h3 class="wp-block-heading">Common Reverse Proxy Problems</h3>



<ul class="wp-block-list">
<li>The upstream hostname is incorrect.</li>
<li>The proxy uses the host port when it should use the container port.</li>
<li>The proxy and application do not share a Docker network.</li>
<li>The application listens only on <code>127.0.0.1</code> inside the container.</li>
<li>The proxy forwards HTTP while the backend expects HTTPS.</li>
<li>The application is unaware that the original request used HTTPS.</li>
<li>The public hostname does not match the certificate.</li>
<li>Both the proxy and application force redirects, creating a loop.</li>
</ul>



<p class="wp-block-paragraph">Test the application directly before blaming the proxy:</p>



<pre class="wp-block-code"><code>curl -I http://127.0.0.1:published-port</code></pre>



<p class="wp-block-paragraph">If the application responds directly but fails through the domain, the reverse proxy, firewall, DNS, or TLS configuration becomes the main area of investigation.</p>



<h2 class="wp-block-heading">An Application Must Listen on the Correct Interface</h2>



<p class="wp-block-paragraph">An application inside a container may start successfully while listening only on <code>127.0.0.1</code>. In that situation, Docker&#8217;s network interface may not be able to reach it.</p>



<p class="wp-block-paragraph">Containerized web applications should usually listen on:</p>



<pre class="wp-block-code"><code>0.0.0.0</code></pre>



<p class="wp-block-paragraph">This allows the process to accept connections through the container&#8217;s network interfaces.</p>



<p class="wp-block-paragraph">This setting is commonly controlled by application arguments or environment variables such as <code>HOST</code>, <code>BIND_ADDRESS</code>, or <code>LISTEN_ADDRESS</code>.</p>



<h2 class="wp-block-heading">Resource Exhaustion Can Look Like a Docker Failure</h2>



<p class="wp-block-paragraph">Containers share the CPU, memory, storage, and network resources of the host. One service can consume enough resources to affect every other workload.</p>



<pre class="wp-block-code"><code>docker stats</code></pre>



<p class="wp-block-paragraph">This provides a live view of CPU, memory, network, and block I/O usage for running containers.</p>



<p class="wp-block-paragraph">On the host, I also check:</p>



<pre class="wp-block-code"><code>free -h
df -h
df -i
uptime</code></pre>



<p class="wp-block-paragraph">Disk space is not the only storage limit. A filesystem can also run out of inodes, preventing new files from being created even when gigabytes remain available.</p>



<p class="wp-block-paragraph">Kernel logs may reveal out-of-memory termination:</p>



<pre class="wp-block-code"><code>dmesg | grep -i -E "out of memory|killed process|oom"</code></pre>



<p class="wp-block-paragraph">If the kernel killed the process, the application logs may end suddenly without a useful explanation.</p>



<h2 class="wp-block-heading">Docker Disk Usage Grows Quietly</h2>



<p class="wp-block-paragraph">Old images, stopped containers, build cache, unused volumes, and unbounded logs can gradually consume the host&#8217;s storage.</p>



<pre class="wp-block-code"><code>docker system df</code></pre>



<p class="wp-block-paragraph">For more detail:</p>



<pre class="wp-block-code"><code>docker system df -v</code></pre>



<p class="wp-block-paragraph">Cleanup commands should be used carefully:</p>



<pre class="wp-block-code"><code>docker image prune
docker container prune
docker builder prune</code></pre>



<p class="wp-block-paragraph">A broad command such as <code>docker system prune</code> can remove more than expected. Never add the volume-removal option unless the contents of unused volumes have been verified and backed up.</p>



<h2 class="wp-block-heading">Container Logs Can Fill the Server</h2>



<p class="wp-block-paragraph">Docker&#8217;s default JSON logging driver can create large log files when applications produce continuous output.</p>



<p class="wp-block-paragraph">Log rotation can be configured in Docker Compose:</p>



<pre class="wp-block-code"><code>logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "5"</code></pre>



<p class="wp-block-paragraph">This limits the number and size of local log files for that container.</p>



<p class="wp-block-paragraph">For production systems, centralized logging with tools such as Grafana Loki, Elasticsearch, OpenSearch, or a managed logging service makes incident investigation considerably easier.</p>



<h2 class="wp-block-heading">Image Architecture and Compatibility Problems</h2>



<p class="wp-block-paragraph">An image built for one CPU architecture may not run correctly on another. This becomes relevant when moving between AMD64 servers, ARM-based cloud instances, Apple Silicon machines, and single-board computers.</p>



<p class="wp-block-paragraph">Check the host architecture:</p>



<pre class="wp-block-code"><code>uname -m</code></pre>



<p class="wp-block-paragraph">Inspect the image:</p>



<pre class="wp-block-code"><code>docker image inspect image-name</code></pre>



<p class="wp-block-paragraph">Errors mentioning an invalid executable format frequently indicate an architecture mismatch.</p>



<p class="wp-block-paragraph">Multi-platform builds can be produced using Docker Buildx:</p>



<pre class="wp-block-code"><code>docker buildx build --platform linux/amd64,linux/arm64 .</code></pre>



<h2 class="wp-block-heading">A New Image Tag Does Not Always Mean a Safe Upgrade</h2>



<p class="wp-block-paragraph">Using <code>latest</code> may be convenient, but it makes deployments less predictable. A future image update can introduce a breaking configuration change, database migration, removed feature, or incompatible dependency.</p>



<p class="wp-block-paragraph">Production deployments are usually safer with an explicit version:</p>



<pre class="wp-block-code"><code>image: grafana/grafana:12.0.2</code></pre>



<p class="wp-block-paragraph">Before upgrading:</p>



<ul class="wp-block-list">
<li>Read the release notes.</li>
<li>Confirm supported upgrade paths.</li>
<li>Back up persistent data.</li>
<li>Record the current working version.</li>
<li>Test the change outside production where possible.</li>
<li>Prepare a rollback procedure.</li>
</ul>



<p class="wp-block-paragraph">Containers make software easier to replace, but they do not make every application upgrade reversible.</p>



<h2 class="wp-block-heading">Dependency Startup Order Is Not Readiness</h2>



<p class="wp-block-paragraph">Docker Compose can start services in a defined order, but starting a database container does not mean the database is immediately ready for connections.</p>



<p class="wp-block-paragraph">An application may launch too early, fail to connect, and exit.</p>



<p class="wp-block-paragraph">Health checks and dependency conditions can improve this:</p>



<pre class="wp-block-code"><code>services:
  database:
    image: postgres:17
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  application:
    image: example/application:1.0
    depends_on:
      database:
        condition: service_healthy</code></pre>



<p class="wp-block-paragraph">The application itself should still handle temporary dependency failures gracefully. Networks fluctuate, databases restart, and external services occasionally become unavailable.</p>



<h2 class="wp-block-heading">Backups Matter More Than Recreating Containers</h2>



<p class="wp-block-paragraph">Application containers are normally easy to recreate. Persistent business data is not.</p>



<p class="wp-block-paragraph">A reliable Docker backup strategy should cover:</p>



<ul class="wp-block-list">
<li>Database-native backups</li>
<li>Named volumes and bind-mounted data</li>
<li>Docker Compose files</li>
<li>Environment files and secrets</li>
<li>Reverse proxy configurations</li>
<li>TLS and certificate configuration</li>
<li>Application-specific settings</li>
<li>A tested restoration procedure</li>
</ul>



<p class="wp-block-paragraph">A backup that has never been restored is only an assumption.</p>



<p class="wp-block-paragraph">Database backups should normally use the database&#8217;s own tools rather than copying active database files blindly. Examples include <code>pg_dump</code> for PostgreSQL and <code>mysqldump</code> or <code>mariadb-dump</code> for MySQL-compatible systems.</p>



<h2 class="wp-block-heading">Monitoring Changes Troubleshooting From Guesswork to Evidence</h2>



<p class="wp-block-paragraph">Without monitoring, administrators usually investigate after users report a failure. By then, the container may have restarted and the original conditions may no longer be visible.</p>



<p class="wp-block-paragraph">Metrics help answer better questions:</p>



<ul class="wp-block-list">
<li>When did memory consumption begin increasing?</li>
<li>Which container experienced the first error?</li>
<li>Was the disk already nearly full?</li>
<li>Did network latency rise before the application failed?</li>
<li>How many times did the service restart?</li>
<li>Did the host run out of memory?</li>
<li>Was the reverse proxy still able to reach the backend?</li>
</ul>



<p class="wp-block-paragraph">Prometheus, Grafana, node-level exporters, container metrics, uptime probes, and centralized logs provide the historical context that individual Docker commands cannot.</p>



<h2 class="wp-block-heading">A Practical Docker Troubleshooting Workflow</h2>



<p class="wp-block-paragraph">When a Docker service fails, I use a sequence rather than jumping between unrelated commands.</p>



<h3 class="wp-block-heading">1. Identify the Exact Symptom</h3>



<ul class="wp-block-list">
<li>Does the container fail to start?</li>
<li>Does it start and then exit?</li>
<li>Is it running but unreachable?</li>
<li>Is the application responding with errors?</li>
<li>Is the public domain failing while the local service works?</li>
<li>Is the problem intermittent?</li>
</ul>



<h3 class="wp-block-heading">2. Check Container Status</h3>



<pre class="wp-block-code"><code>docker ps -a</code></pre>



<h3 class="wp-block-heading">3. Read the Logs</h3>



<pre class="wp-block-code"><code>docker logs --tail 200 container-name</code></pre>



<h3 class="wp-block-heading">4. Inspect Runtime Configuration</h3>



<pre class="wp-block-code"><code>docker inspect container-name</code></pre>



<h3 class="wp-block-heading">5. Validate Docker Compose</h3>



<pre class="wp-block-code"><code>docker compose config</code></pre>



<h3 class="wp-block-heading">6. Check Host Resources</h3>



<pre class="wp-block-code"><code>docker stats
free -h
df -h
df -i</code></pre>



<h3 class="wp-block-heading">7. Test Internal Connectivity</h3>



<p class="wp-block-paragraph">Enter the relevant container and test DNS resolution, ports, and HTTP endpoints from the same network context as the application.</p>



<h3 class="wp-block-heading">8. Check Volumes and Permissions</h3>



<p class="wp-block-paragraph">Confirm that mounts point to the expected locations and that the container user can read or write them as required.</p>



<h3 class="wp-block-heading">9. Isolate the Reverse Proxy</h3>



<p class="wp-block-paragraph">Test the backend directly. If the backend works, investigate the proxy, DNS, TLS, firewall, and forwarded headers.</p>



<h3 class="wp-block-heading">10. Change One Thing at a Time</h3>



<p class="wp-block-paragraph">Multiple simultaneous changes make it difficult to identify the actual solution. Apply one correction, test it, and record the result.</p>



<h2 class="wp-block-heading">Commands I Frequently Use During Docker Investigations</h2>



<pre class="wp-block-code"><code># Show running containers
docker ps

# Show all containers
docker ps -a

# Read recent logs
docker logs --tail 100 container-name

# Follow logs
docker logs -f container-name

# Inspect a container
docker inspect container-name

# Enter a container
docker exec -it container-name sh

# View live resource use
docker stats

# List networks
docker network ls

# Inspect a network
docker network inspect network-name

# List volumes
docker volume ls

# Inspect a volume
docker volume inspect volume-name

# Validate Compose configuration
docker compose config

# View Compose service status
docker compose ps

# Restart one service
docker compose restart service-name

# Recreate one service
docker compose up -d --force-recreate service-name

# View Docker disk usage
docker system df

# View host listening ports
ss -tulpn

# Check disk usage
df -h

# Check inode usage
df -i

# Check memory
free -h</code></pre>



<h2 class="wp-block-heading">Docker Troubleshooting Mistakes to Avoid</h2>



<h3 class="wp-block-heading">Deleting Containers Before Reading Their Logs</h3>



<p class="wp-block-paragraph">This removes evidence and often leaves the original problem unresolved.</p>



<h3 class="wp-block-heading">Using Docker System Prune Without Reviewing the Impact</h3>



<p class="wp-block-paragraph">Cleanup commands can remove useful images, build cache, stopped containers, and potentially important storage.</p>



<h3 class="wp-block-heading">Using Latest Everywhere</h3>



<p class="wp-block-paragraph">Uncontrolled image updates reduce reproducibility and can introduce breaking changes unexpectedly.</p>



<h3 class="wp-block-heading">Opening Every Port Publicly</h3>



<p class="wp-block-paragraph">Internal services should remain internal unless external access is genuinely required.</p>



<h3 class="wp-block-heading">Setting Permissions to 777</h3>



<p class="wp-block-paragraph">This weakens security and avoids fixing the actual ownership or access-control issue.</p>



<h3 class="wp-block-heading">Restarting the Entire Server Too Early</h3>



<p class="wp-block-paragraph">A restart may temporarily restore service while destroying evidence about the original failure.</p>



<h3 class="wp-block-heading">Changing Several Variables at Once</h3>



<p class="wp-block-paragraph">Even when the issue disappears, it becomes impossible to know which change solved it.</p>



<h2 class="wp-block-heading">Docker Compose Is Also Infrastructure Documentation</h2>



<p class="wp-block-paragraph">A well-organized Compose file documents the structure of an application environment.</p>



<ul class="wp-block-list">
<li>Which services exist</li>
<li>Which image versions are deployed</li>
<li>Which networks connect them</li>
<li>Which ports are externally accessible</li>
<li>Where persistent data is stored</li>
<li>Which environment variables are required</li>
<li>Which services depend on others</li>
<li>Which health checks are available</li>
<li>How containers restart after failure</li>
</ul>



<p class="wp-block-paragraph">Six months after a deployment, this information is often more useful than a separate document that was never updated.</p>



<p class="wp-block-paragraph">Readable service names, comments for non-obvious decisions, pinned image versions, organized environment files, and clear volume names make future troubleshooting much faster.</p>



<h2 class="wp-block-heading">What Docker Failures Actually Teach</h2>



<p class="wp-block-paragraph">Some of the most useful infrastructure lessons come from deployments that did not work as expected.</p>



<p class="wp-block-paragraph">A failed container may teach you how Linux permissions interact with numeric user IDs. A 502 error may reveal the difference between host ports and container ports. A disappearing database may demonstrate why persistent volumes matter. An unreachable service may expose a misunderstanding about Docker DNS or network boundaries.</p>



<p class="wp-block-paragraph">These incidents are frustrating, but they build practical knowledge that clean tutorial environments rarely provide.</p>



<p class="wp-block-paragraph">Production systems do not normally fail in textbook ways. Several small issues may combine: a nearly full disk, oversized logs, an automatic image upgrade, and an aggressive restart policy can create one confusing incident.</p>



<h2 class="wp-block-heading">Final Thoughts</h2>



<p class="wp-block-paragraph">Docker makes application deployment more portable, repeatable, and manageable, but it does not remove the need to understand the systems underneath it.</p>



<p class="wp-block-paragraph">Effective Docker troubleshooting requires knowledge of Linux, networking, storage, process management, application configuration, security, and monitoring.</p>



<p class="wp-block-paragraph">The most important habit is simple: follow the evidence.</p>



<p class="wp-block-paragraph">Check the container state. Read the logs. Inspect the configuration. Test the network from the correct location. Verify volumes and permissions. Check the host&#8217;s resources. Separate the application from the proxy. Change one thing at a time.</p>



<p class="wp-block-paragraph">The next time a Docker container fails, resist the urge to delete everything and begin again. The answer is usually already present in the logs, runtime configuration, network design, storage mapping, or host environment.</p>



<p class="wp-block-paragraph">Docker troubleshooting becomes much less intimidating once every failure is treated as an investigation rather than an emergency.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Frequently Asked Questions About Docker Issues</h2>



<h3 class="wp-block-heading">Why does my Docker container exit immediately?</h3>



<p class="wp-block-paragraph">A Docker container exits when its main process stops. Common causes include invalid configuration, missing environment variables, permission errors, failed dependency connections, and incorrect startup commands. Use <code>docker logs</code> and <code>docker inspect</code> to identify the cause.</p>



<h3 class="wp-block-heading">Why is my Docker container running but not accessible?</h3>



<p class="wp-block-paragraph">The application may be listening on the wrong interface, the port may not be published correctly, a firewall may be blocking traffic, or the reverse proxy may point to the wrong hostname or port. Test the application both inside the container and directly through the host port.</p>



<h3 class="wp-block-heading">Why can one Docker container not connect to another?</h3>



<p class="wp-block-paragraph">The containers may not share a Docker network, the wrong service name may be used, or the destination service may not be listening on the expected port. Inside Docker, use the Compose service name rather than <code>localhost</code>.</p>



<h3 class="wp-block-heading">How do I find which process is using a Docker port?</h3>



<p class="wp-block-paragraph">Run <code>ss -tulpn</code> on the Docker host and filter for the relevant port. If the host port is already occupied, change the host side of the Docker port mapping or stop the conflicting service.</p>



<h3 class="wp-block-heading">Can restarting Docker solve container problems?</h3>



<p class="wp-block-paragraph">Restarting Docker may temporarily restore a service, but it can also hide the original cause. Logs, container state, disk space, memory, network connectivity, and volume permissions should be checked before restarting the Docker daemon or server.</p>



<h3 class="wp-block-heading">How can I prevent Docker logs from filling the disk?</h3>



<p class="wp-block-paragraph">Configure log rotation using the Docker logging options, such as <code>max-size</code> and <code>max-file</code>. Production environments may also benefit from centralized logging with Grafana Loki, OpenSearch, Elasticsearch, or a managed logging platform.</p>



<h3 class="wp-block-heading">Should I use the latest Docker image tag?</h3>



<p class="wp-block-paragraph">The latest tag is convenient but unpredictable. Pinning a tested image version improves repeatability and makes upgrades and rollbacks easier to control.</p>



<h3 class="wp-block-heading">Are Docker volumes automatically backed up?</h3>



<p class="wp-block-paragraph">No. Docker volumes provide persistent storage, but Docker does not automatically create external backups. Important volumes and databases need a separate, tested backup and restoration process.</p>

<p>The post <a href="https://john-nessime.com/blog/devops/docker-issues-troubleshooting-real-world-solutions/">Docker Issues &amp; Troubleshooting: Real-World Solutions</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://john-nessime.com/blog/devops/docker-issues-troubleshooting-real-world-solutions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
