<?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>Networking | John Nessime</title>
	<atom:link href="https://john-nessime.com/blog/tag/networking/feed/" rel="self" type="application/rss+xml" />
	<link>https://john-nessime.com/blog/tag/networking/</link>
	<description>Cloud, DevOps, Data &#38; AI — Built, Tested, Explained</description>
	<lastBuildDate>Thu, 29 Oct 2026 06:05:05 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.4</generator>

<image>
	<url>https://john-nessime.com/blog/wp-content/uploads/2026/07/cropped-jn-32x32.png</url>
	<title>Networking | John Nessime</title>
	<link>https://john-nessime.com/blog/tag/networking/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Salesforce AWS PrivateLink: The Failures Nobody Warns You About</title>
		<link>https://john-nessime.com/blog/technical-guides/salesforce-aws-privatelink/</link>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Thu, 29 Oct 2026 06:05:05 +0000</pubDate>
				<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[Networking]]></category>
		<category><![CDATA[Technical Guides]]></category>
		<category><![CDATA[Web Security]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[Data Integration]]></category>
		<category><![CDATA[DNS]]></category>
		<category><![CDATA[Hyperforce]]></category>
		<category><![CDATA[Interface Endpoint]]></category>
		<category><![CDATA[Named Credentials]]></category>
		<category><![CDATA[Network Isolation]]></category>
		<category><![CDATA[Private Hosted Zone]]></category>
		<category><![CDATA[PrivateLink]]></category>
		<category><![CDATA[Rate Limiting]]></category>
		<category><![CDATA[Route 53]]></category>
		<category><![CDATA[Salesforce]]></category>
		<category><![CDATA[Salesforce Private Connect]]></category>
		<category><![CDATA[SNI]]></category>
		<category><![CDATA[TLS]]></category>
		<category><![CDATA[Transit Gateway]]></category>
		<category><![CDATA[VPC]]></category>
		<category><![CDATA[VPC Flow Logs]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=330</guid>

					<description><![CDATA[<p>Salesforce Private Connect over AWS PrivateLink does not fail loudly. It fails by working: green statuses, successful callouts, and packets still crossing the public internet because one DNS record was wrong. Here are the six failure families that actually bite, and how to prove your traffic is private.</p>
<p>The post <a href="https://john-nessime.com/blog/technical-guides/salesforce-aws-privatelink/">Salesforce AWS PrivateLink: The Failures Nobody Warns You About</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">The ticket said the integration was &#8220;already private.&#8221; Someone had bought the license, clicked through the setup wizard, seen a green status in the org, and closed the epic. Six weeks later a security review pulled VPC flow logs and asked a simple question: why is this traffic still leaving through the NAT gateway?</p>



<p class="wp-block-paragraph">That is the failure mode worth understanding before you touch anything. A Salesforce AWS PrivateLink setup does not fail loudly. It fails by working. The callout succeeds, the payload arrives, the dashboard is green, and the packets went over the public internet anyway because one DNS record was wrong. Nothing in either console tells you.</p>



<p class="wp-block-paragraph">This post covers what actually breaks when you wire Salesforce to AWS over private connectivity: the DNS trap above, the throughput ceiling nobody budgets for, the TLS and port constraints, why your sandbox cannot rehearse the change, and how to prove after the fact that traffic really is staying on the AWS network.</p>



<h2 class="wp-block-heading">What Private Connect actually gives you</h2>



<p class="wp-block-paragraph">Salesforce Private Connect is the product name. AWS PrivateLink is the underlying mechanism. Salesforce runs a managed transit VPC in each supported AWS region, and you attach to it: either your VPC creates an interface endpoint pointing at their endpoint service, or their transit VPC creates an endpoint pointing at yours.</p>



<p class="wp-block-paragraph">Two things people consistently get wrong here.</p>



<p class="wp-block-paragraph">First, the direction naming is from the org&#8217;s point of view, not yours. <strong>Inbound</strong> means traffic entering Salesforce, so your AWS workload calling the Salesforce REST or Bulk API. <strong>Outbound</strong> means Salesforce calling out to something you host. If you are an AWS engineer, &#8220;inbound&#8221; is the one that feels like outbound to you. Half the confused meetings I have seen on this trace back to that single word.</p>



<p class="wp-block-paragraph">Second, Private Connect only builds the pipe. It does not move data. You still need Apex callouts, External Services, Platform Events, MuleSoft, Amazon AppFlow, or whatever else was already doing the work. If your integration is broken today, a private path will carry the broken requests privately.</p>



<blockquote class="is-layout-flow wp-block-quote-is-layout-flow"><p>Private connectivity is a network property, not an application feature. Nothing above layer 4 changes. Plan the rollout as a routing change, not an integration project.</p></blockquote>



<h2 class="wp-block-heading">Failure family one: DNS that quietly resolves to the public internet</h2>



<p class="wp-block-paragraph">This is the one that produced the ticket at the top, and it is by far the most common way a Salesforce AWS PrivateLink deployment ends up decorative.</p>



<p class="wp-block-paragraph">For the inbound case, your application calls a hostname like <code>yourorg.my.salesforce.com</code>. That hostname resolves publicly. Creating the interface endpoint does not change that. Unless something inside your VPC overrides the answer, the resolver returns Salesforce&#8217;s public addresses and your traffic takes the normal internet path. The endpoint sits there costing you money and carrying nothing.</p>



<p class="wp-block-paragraph">The override is a Route 53 private hosted zone, associated with the VPC that holds the endpoint, containing a record that points the org hostname at the endpoint. Salesforce&#8217;s own walkthrough names the zone <code>my.salesforce.com</code> and puts the org-specific record inside it.</p>



<p class="wp-block-paragraph">Three things have to line up or the override silently does nothing:</p>



<ul class="wp-block-list"><li><strong>The zone is associated with the right VPC.</strong> A private hosted zone answers only for VPCs it is attached to. Query it from anywhere else and the request falls through to public recursion.</li><li><strong>The VPC has DNS support and DNS hostnames enabled.</strong> Both attributes must be on, or the VPC resolver will not serve the zone at all.</li><li><strong>Callers actually use the VPC resolver.</strong> Workloads pinned to an external resolver, containers with a hardcoded <code>nameserver</code> line, or on-premises clients arriving over Direct Connect will bypass the zone entirely unless you front it with a Route 53 Resolver inbound endpoint.</li></ul>



<p class="wp-block-paragraph">There is a sharp edge in the other direction too. If a private hosted zone matches the queried name but contains no record of the right type, Route 53 returns NXDOMAIN rather than falling through to public DNS. So a half-finished zone does not degrade gracefully, it takes the hostname down. Creating the zone and the record in separate change windows is how teams turn a routing improvement into an outage.</p>



<p class="wp-block-paragraph">Verify from inside the VPC, not from your laptop:</p>



<pre class="wp-block-code"><code># Run this on an instance in the VPC associated with the private zone.
dig +short yourorg.my.salesforce.com

# You want RFC 1918 addresses from your endpoint subnets.
# Public addresses mean the private zone is not being consulted.

# Confirm which resolver answered:
dig yourorg.my.salesforce.com | grep -i "SERVER:"</code></pre>



<h2 class="wp-block-heading">Failure family two: the throughput ceiling nobody reads</h2>



<p class="wp-block-paragraph">Private Connect is rate limited, and the default entitlement is small enough to catch people out badly.</p>



<p class="wp-block-paragraph">Salesforce documents an initial license entitlement of 225 MB of data per hour, measured separately for inbound and outbound, with an add-on ceiling far higher. The allowance does not roll over. It resets on the hour.</p>



<p class="wp-block-paragraph">Now consider what a nightly Bulk API extract looks like. A few gigabytes pushed in a twenty minute window is entirely normal and completely incompatible with a 225 MB hourly bucket. The two directions behave differently when you hit the wall, which matters more than the number itself:</p>



<ul class="wp-block-list"><li><strong>Outbound is enforced technically.</strong> Exceed it and outbound traffic stops until the counter resets at the top of the next hour. Your Apex callouts start failing. This is a production incident.</li><li><strong>Inbound is enforced contractually.</strong> Salesforce documents that you are responsible for monitoring and enforcing the inbound limit yourself. Nothing throttles you at the network layer, but you are outside your agreement, and standard enterprise API limits still apply on top.</li></ul>



<p class="wp-block-paragraph">The outbound allowance is visible through the Limits API as <code>PrivateConnectOutboundCalloutHourlyLimitMB</code>, which is what makes it monitorable. Poll it, put it on a dashboard next to your other API limit metrics in Grafana or CloudWatch, and alert well before exhaustion rather than after the callouts fail.</p>



<p class="wp-block-paragraph">If your volume genuinely exceeds the entitlement, the honest options are buying more, reshaping the workload to spread across hours, or accepting that bulk movement stays on a different path and Private Connect covers only your latency-sensitive transactional traffic. That last one is a legitimate architecture, not a failure.</p>



<h2 class="wp-block-heading">Failure family three: TLS, SNI, and the port you cannot use</h2>



<p class="wp-block-paragraph">Private Connect inbound supports port 443 only. Port 8443 is explicitly not supported because Salesforce uses it for mutual TLS. If your integration depends on client certificate authentication on that port, this design does not cover it, and finding out during cutover is expensive.</p>



<p class="wp-block-paragraph">The subtler problem is SNI. Salesforce documents that Hyperforce requires callers to include the Server Name Indication extension in the TLS ClientHello so the right certificate can be served, and that clients calling a hostname outside the <code>*.my.salesforce.com</code> or <code>*.sandbox.my.salesforce.com</code> patterns will hit HTTPS errors. Browsers always send SNI. Older HTTP libraries, embedded clients, and some middleware stacks do not.</p>



<p class="wp-block-paragraph">The reason this bites specifically during a PrivateLink migration is that you are changing what the name resolves to while keeping the name identical. A client that was quietly relying on the public path&#8217;s behavior now hits a different frontend. Test the handshake explicitly:</p>



<pre class="wp-block-code"><code># From inside the VPC, forcing SNI to the org hostname.
openssl s_client -connect yourorg.my.salesforce.com:443 
  -servername yourorg.my.salesforce.com &lt;/dev/null

# Compare with SNI omitted. If this one fails and the above
# succeeds, your client library is the problem, not the network.
openssl s_client -connect yourorg.my.salesforce.com:443 &lt;/dev/null</code></pre>



<p class="wp-block-paragraph">On the outbound side the mirror image applies. Salesforce is the client calling your endpoint service, and it will validate your certificate against the hostname it was told to use. If your Network Load Balancer terminates TLS with a certificate whose subject alternative names do not include the private DNS name you registered, the callout fails on hostname verification. Issue the certificate for a name you control and that matches what the named credential targets.</p>



<h2 class="wp-block-heading">Failure family four: you cannot rehearse this properly</h2>



<p class="wp-block-paragraph">This one deserves its own section because it changes how you plan the work.</p>



<p class="wp-block-paragraph">Private connections are not copied from production into sandboxes. You recreate them. And the ability to recreate them depends on sandbox type:</p>



<ul class="wp-block-list"><li><strong>Full and Partial Copy sandboxes:</strong> you can create connections and provision them. This is where genuine rehearsal happens.</li><li><strong>Developer and Developer Pro sandboxes:</strong> you can create connections but cannot provision them.</li><li><strong>Scratch orgs and Developer orgs:</strong> create only, no provisioning.</li></ul>



<p class="wp-block-paragraph">If your organization only has Developer sandboxes, there is no lower environment where the private path can be exercised end to end. That is a procurement conversation, not an engineering one, and it needs to happen at design time. Budget for a Full or Partial Copy sandbox or accept that production is your first real test.</p>



<p class="wp-block-paragraph">Related trap: connections are packageable while unprovisioned, and unprovisioned connections do not consume a license. That is useful for shipping configuration through a deployment pipeline, but it also means a successful deploy proves nothing about connectivity.</p>



<h2 class="wp-block-heading">Failure family five: license arithmetic and connection identity</h2>



<p class="wp-block-paragraph">Each Private Connect license covers one provisioned connection in each direction. Each connection is a one-to-one mapping between an org ID and a VPC endpoint ID.</p>



<p class="wp-block-paragraph">Read that mapping carefully, because it is where multi-account AWS estates get expensive. Separate VPCs mean separate endpoints mean separate connections mean separate licenses. Separate orgs mean the same again. A design with three application VPCs across two orgs is not one connection, it is six.</p>



<p class="wp-block-paragraph">The architectural answer is usually a shared services or egress VPC that owns the endpoint, with the application VPCs reaching it through a Transit Gateway. You pay for one endpoint and route to it. This is the same centralized-endpoint pattern you would already use for interface endpoints to AWS services, and the reasoning is identical: endpoints are billed per attachment, so consolidate them.</p>



<p class="wp-block-paragraph">Four inbound connections consume four licenses. If you hold four licenses, you have used all of them and have none left for outbound. Count both directions before the renewal conversation.</p>



<h2 class="wp-block-heading">Failure family six: the AWS side of an outbound connection</h2>



<p class="wp-block-paragraph">For outbound, you are the service provider. Salesforce&#8217;s transit VPC creates the endpoint into your account, which means you have to build a proper endpoint service.</p>



<p class="wp-block-paragraph">The sequence, in the order that avoids rework:</p>



<ol class="wp-block-list"><li>Stand up a Network Load Balancer in front of your service, across at least two Availability Zones.</li><li>Create the VPC endpoint service backed by that NLB.</li><li>Copy the IAM role ARN from the Private Connect setup page in Salesforce for your region, and add it to the allowed principals on the endpoint service. Without this, Salesforce cannot connect and the failure looks like a generic timeout.</li><li>Register a private DNS name on the endpoint service and complete domain ownership verification.</li><li>Create the outbound connection in Salesforce using the endpoint service name, then sync it to pick up the AWS-side status.</li><li>Associate the connection with a named credential. Callouts only take the private path when they go through a named credential bound to that connection.</li></ol>



<p class="wp-block-paragraph">Step four is where people lose an afternoon. Domain verification requires a TXT record in a <em>public</em> zone, because AWS queries the public name servers registered at your domain&#8217;s registrar. Put the record in a private hosted zone and verification sits in pending forever with no useful error.</p>



<pre class="wp-block-code"><code># Register the private DNS name on your endpoint service.
aws ec2 modify-vpc-endpoint-service-configuration 
  --service-id vpce-svc-EXAMPLE 
  --private-dns-name "sf-integration.example.com"

# Read back the TXT record name and value AWS expects.
aws ec2 describe-vpc-endpoint-service-configurations 
  --service-ids vpce-svc-EXAMPLE 
  --query 'ServiceConfigurations[0].PrivateDnsNameConfiguration'

# After publishing the TXT record in your PUBLIC zone:
aws ec2 start-vpc-endpoint-service-private-dns-verification 
  --service-id vpce-svc-EXAMPLE</code></pre>



<p class="wp-block-paragraph">One detail worth knowing: if verification status later changes away from verified, new connection requests are refused while existing connections keep working. So a lapsed TXT record does not break you immediately. It breaks you the next time something reconnects, which is usually during an unrelated incident.</p>



<h2 class="wp-block-heading">What this costs, mechanically</h2>



<p class="wp-block-paragraph">Rates change, so model the mechanism rather than memorizing figures from a blog post.</p>



<ul class="wp-block-list"><li><strong>Interface endpoint hours.</strong> Billed per endpoint, per Availability Zone, per hour, whether or not traffic flows. A three-AZ deployment is three billable attachments.</li><li><strong>Data processing.</strong> Per GB through the endpoint, on a tiered scale that drops as regional volume rises.</li><li><strong>Cross-AZ transfer through interface endpoints.</strong> AWS removed this charge, so multi-AZ endpoint placement is a resilience decision rather than a cost trade-off.</li><li><strong>Cross-region.</strong> Still charged, in both directions, plus a per-hour fee on the endpoint service for each remote region with attached endpoints. Match your org region to your VPC region if you can.</li><li><strong>Provider-side infrastructure.</strong> For outbound, the NLB and its targets are your bill, not PrivateLink&#8217;s.</li><li><strong>The Salesforce license.</strong> Separate from all of the above, priced per connection.</li></ul>



<p class="wp-block-paragraph">Compare honestly against what you are replacing. If the alternative is a NAT gateway carrying the same traffic, NAT has both an hourly charge and a per-GB processing charge of its own, and the comparison is often closer than people assume. If the alternative is a site-to-site VPN or a commercial network overlay, the calculus changes again. PrivateLink wins on the security posture, not automatically on the invoice.</p>



<h2 class="wp-block-heading">Troubleshooting: proving the traffic is actually private</h2>



<p class="wp-block-paragraph">Do not trust a green status field. Prove it with evidence from the data path.</p>



<ol class="wp-block-list"><li><strong>Resolve the hostname from inside the VPC.</strong> Private addresses from your endpoint subnets, or nothing else matters.</li><li><strong>Check the endpoint state.</strong> <code>aws ec2 describe-vpc-endpoints</code> should show the endpoint available, with an ENI in each subnet you expect.</li><li><strong>Read VPC flow logs.</strong> Filter for your application&#8217;s source addresses and confirm the destination is the endpoint ENI, not a NAT gateway or internet gateway address. This is the actual proof.</li><li><strong>Check the security group on the endpoint.</strong> Interface endpoints carry security groups. If 443 is not permitted from your application subnets, connections hang rather than refuse, which reads as a Salesforce problem in your logs.</li><li><strong>For outbound, sync the connection in Salesforce</strong> before believing its status. The displayed state is the last polled AWS state, not live.</li><li><strong>Test the callout through the named credential.</strong> A direct callout to a hardcoded URL will not use the private path even when everything else is correct.</li></ol>



<p class="wp-block-paragraph">Two symptom patterns worth recognizing. Connections that hang and time out point at security groups or NLB target health. Connections that fail fast with a TLS error point at certificate naming or SNI. They are diagnosed in completely different places.</p>



<h2 class="wp-block-heading">Common mistakes</h2>



<ul class="wp-block-list"><li>Treating the setup wizard&#8217;s success message as proof that traffic is private. It proves the control plane is configured.</li><li>Creating the private hosted zone in one change and the record in another, producing NXDOMAIN for the org hostname in between.</li><li>Publishing the domain verification TXT record in a private zone instead of the public one.</li><li>Forgetting the Salesforce IAM role in the allowed principals list on the endpoint service, then debugging it as a network problem.</li><li>Sizing the connection for peak batch throughput without checking the hourly data entitlement.</li><li>Assuming sessions locked to their originating IP address will keep working. That behavior is documented as unsupported with Private Connect, along with asynchronous callouts using Apex continuations.</li><li>Building one endpoint per application VPC and discovering the license count at renewal.</li><li>Leaving callouts on hardcoded endpoints instead of named credentials, so outbound traffic never enters the private path.</li></ul>



<h2 class="wp-block-heading">Best practices for a Salesforce AWS PrivateLink rollout</h2>



<ul class="wp-block-list"><li>Inventory every integration touching the org first, with direction, protocol, hostname, and peak volume per hour. The inventory determines the design.</li><li>Centralize endpoints in a shared services VPC and route from application VPCs through Transit Gateway rather than duplicating endpoints and licenses.</li><li>Put the endpoint in the same region as the org. Cross-region private connectivity works but gives up most of the latency benefit and adds transfer charges.</li><li>Enable VPC flow logs before cutover so you have a clean before-and-after picture. Ship them somewhere you can query, whether that is CloudWatch Logs Insights, Athena over S3, or a platform like Grafana Cloud or Datadog.</li><li>Alert on the outbound hourly limit as a percentage of entitlement, not on callout failures. By the time callouts fail you are already down.</li><li>Terraform the AWS side. The endpoint, the endpoint service, the allowed principals, the private hosted zone, and the records all belong in code. The Salesforce side is click-configured, so document it in the same repository as a runbook.</li><li>Keep a documented rollback: the change that reverts you to public routing is removing or disassociating the private hosted zone, and it should be a one-step action you have practiced.</li><li>Review the configuration on a schedule. Private routing is invisible in normal operation and easy to break with an unrelated DNS or VPC change.</li></ul>



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



<h2 class="wp-block-heading">Frequently asked questions</h2>



<h3 class="wp-block-heading">Does Private Connect encrypt my traffic?</h3>



<p class="wp-block-paragraph">Your traffic is already encrypted in transit by TLS. What private connectivity changes is the path, not the cipher. The value is that packets stay on the AWS network rather than traversing the public internet, which removes an entire class of exposure and simplifies a lot of compliance conversations. It is not a substitute for TLS, and it does not authenticate anything.</p>



<h3 class="wp-block-heading">Can I use Private Connect to access the Salesforce user interface privately?</h3>



<p class="wp-block-paragraph">No. This is API-path connectivity for integrations. Browser access to the org is a separate concern, and if that is what you need, you are looking at conditional access policies, login IP ranges, or a corporate network solution rather than PrivateLink.</p>



<h3 class="wp-block-heading">What Salesforce features actually route through an outbound connection?</h3>



<p class="wp-block-paragraph">Salesforce documents outbound support for Apex callouts, External Services, Platform Events, Change Data Capture, CRM Analytics, the OData 4.01 adapter and custom adapter for Salesforce Connect, the Salesforce Connect SQL adapter for Amazon Athena, and Agent Actions backed by External Services. Inbound covers the supported public APIs. Anything not on the current list should be verified against the documentation before you design around it, because that list grows.</p>



<h3 class="wp-block-heading">Do I need Hyperforce for this?</h3>



<p class="wp-block-paragraph">Salesforce states that Private Connect works whether the org sits in first-party data centers or on Hyperforce. That said, Hyperforce brings its own requirements, particularly the SNI behavior described above, so confirm where your org actually runs before you start debugging TLS.</p>



<h3 class="wp-block-heading">How do I connect on-premises systems, not just AWS workloads?</h3>



<p class="wp-block-paragraph">Bring the on-premises network into AWS first with Direct Connect or a site-to-site VPN, then use the interface endpoint from there. The extra work is DNS: on-premises resolvers need a Route 53 Resolver inbound endpoint to see the private hosted zone, otherwise they resolve the org hostname publicly and you are back to the first failure family.</p>



<h3 class="wp-block-heading">Can I use one connection for several Salesforce orgs?</h3>



<p class="wp-block-paragraph">No. A connection is a one-to-one mapping between an org ID and a VPC endpoint ID, so each org needs its own provisioned connection and its own license, even if they all point at the same VPC.</p>



<h3 class="wp-block-heading">Is a VPN a reasonable alternative?</h3>



<p class="wp-block-paragraph">For Salesforce specifically, no. Salesforce does not terminate a customer VPN into the org, so PrivateLink is the mechanism on offer. A VPN or a commercial overlay such as NordLayer solves a different problem, which is getting your own users and sites onto your own network. Where those tools genuinely overlap is the on-premises leg, and even there Direct Connect is usually the better answer for sustained integration traffic.</p>



<h2 class="wp-block-heading">The one thing to remember</h2>



<p class="wp-block-paragraph">A Salesforce AWS PrivateLink deployment that is misconfigured looks exactly like one that works. Same green statuses, same successful callouts, same clean application logs. The only difference is which path the packets took, and the only place that difference is visible is your flow logs.</p>



<p class="wp-block-paragraph">So build the verification step into the project rather than the postmortem. Resolve the hostname from inside the VPC, read the flow logs, confirm the destination is the endpoint ENI, and keep the check running afterwards. Everything else in this post is detail. That one habit is what separates private connectivity from a line item that says private.</p>



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



<h2 class="wp-block-heading">Need help wiring Salesforce to AWS privately?</h2>



<p class="wp-block-paragraph">This is the kind of work where a second pair of eyes on the DNS and the flow logs saves a security review later. I help teams with:</p>



<ul class="wp-block-list"><li>Designing the endpoint topology, including centralized endpoints behind Transit Gateway so you are not buying a connection per VPC.</li><li>Building and verifying the Route 53 private hosted zone override, including Resolver inbound endpoints for on-premises callers.</li><li>Standing up the endpoint service side for outbound connections: NLB, allowed principals, private DNS name registration, and domain verification.</li><li>Proving traffic is genuinely private with VPC flow log analysis, before and after cutover.</li><li>Instrumenting the hourly data entitlement so you find out about throttling from a dashboard rather than from failed callouts.</li><li>Codifying the AWS side in Terraform or OpenTofu, with a documented rollback to public routing.</li></ul>



<p class="wp-block-paragraph">If you are mid-way through one of these and something is not behaving, send me the <code>dig</code> output from inside the VPC, a flow log sample, or your endpoint service configuration and I will tell you what I see.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://www.upwork.com/freelancers/~01f15a912ad84a6620" target="_blank" rel="noreferrer noopener">Work with me on Upwork</a></div>
</div>
<p>The post <a href="https://john-nessime.com/blog/technical-guides/salesforce-aws-privatelink/">Salesforce AWS PrivateLink: The Failures Nobody Warns You About</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Docker Issues &#038; Troubleshooting: Real-World Solutions</title>
		<link>https://john-nessime.com/blog/devops/docker-issues-troubleshooting-real-world-solutions/</link>
		
		<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>
					
		
		
			</item>
	</channel>
</rss>
